diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 00000000000..ff3f1ffc4c7 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +> 0.5%, last 3 major versions, Firefox ESR, not dead diff --git a/.eslintrc.json b/.eslintrc.json index 2b52485689a..1a3849aee30 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,6 +97,11 @@ // Node "./libs/node/**/*", + //Generator + "./libs/tools/generator/components/**/*", + "./libs/tools/generator/core/**/*", + "./libs/tools/generator/extensions/**/*", + // Import/export "./libs/importer/**/*", "./libs/tools/export/vault-export/vault-export-core/**/*" @@ -182,6 +187,33 @@ ] } }, + { + "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/src/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { "patterns": ["@bitwarden/generator-extensions/*", "src/**/*"] } + ] + } + }, { "files": ["libs/tools/export/vault-export/vault-export-core/src/**/*.ts"], "rules": { @@ -218,6 +250,12 @@ "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/vault/src/**/*.ts"], "rules": { diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2ec9d3b1b40..118f9dab277 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -52,6 +52,7 @@ apps/cli/src/admin-console @bitwarden/team-admin-console-dev apps/desktop/src/admin-console @bitwarden/team-admin-console-dev apps/web/src/app/admin-console @bitwarden/team-admin-console-dev bitwarden_license/bit-web/src/app/admin-console @bitwarden/team-admin-console-dev +bitwarden_license/bit-cli/src/admin-console @bitwarden/team-admin-console-dev libs/angular/src/admin-console @bitwarden/team-admin-console-dev libs/common/src/admin-console @bitwarden/team-admin-console-dev libs/admin-console @bitwarden/team-admin-console-dev diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index cbd45e24dc5..89395888365 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -11,6 +11,7 @@ ./libs/platform/README.md ./libs/tools/README.md ./libs/tools/export/vault-export/README.md +./libs/tools/send/README.md ./libs/vault/README.md ./README.md ./LICENSE_BITWARDEN.txt diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 5436f976842..5e333b3b58a 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -13,6 +13,7 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' push: branches: - 'main' @@ -25,6 +26,7 @@ on: - '!*.md' - '!*.txt' - '.github/workflows/build-cli.yml' + - 'bitwarden_license/bit-cli/**' workflow_dispatch: inputs: {} @@ -65,7 +67,7 @@ jobs: os: [ { base: "linux", distro: "ubuntu-22.04" }, - { base: "mac", distro: "macos-11" } + { base: "mac", distro: "macos-13" } ] license_type: [ @@ -78,8 +80,8 @@ jobs: env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - _WIN_PKG_FETCH_VERSION: 18.5.0 - _WIN_PKG_VERSION: 3.4 + _WIN_PKG_FETCH_VERSION: 20.11.1 + _WIN_PKG_VERSION: 3.5 steps: - name: Checkout repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -155,8 +157,8 @@ jobs: env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} - _WIN_PKG_FETCH_VERSION: 18.5.0 - _WIN_PKG_VERSION: 3.4 + _WIN_PKG_FETCH_VERSION: 20.11.1 + _WIN_PKG_VERSION: 3.5 steps: - name: Checkout repo uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -178,7 +180,7 @@ jobs: shell: pwsh run: | cd $HOME - $fetchedUrl = "https://github.com/vercel/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64" + $fetchedUrl = "https://github.com/yao-pkg/pkg-fetch/releases/download/v$env:_WIN_PKG_VERSION/node-v$env:_WIN_PKG_FETCH_VERSION-win-x64" New-Item -ItemType directory -Path .\.pkg-cache New-Item -ItemType directory -Path .\.pkg-cache\v$env:_WIN_PKG_VERSION Invoke-RestMethod -Uri $fetchedUrl ` diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index e73f882bb40..bebf7b5646c 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -444,10 +444,7 @@ jobs: macos-build: name: MacOS Build - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} @@ -605,10 +602,7 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: - browser-build - macos-build @@ -814,10 +808,7 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: - browser-build - macos-build @@ -1014,11 +1005,7 @@ jobs: macos-package-dev: name: MacOS Package Dev Release Asset - if: false # We need to look into how code signing works for dev - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: - browser-build - macos-build @@ -1188,14 +1175,15 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Zip masdev asset - working-directory: ./dist/mas-dev-universal - run: zip -r Bitwarden-${{ env.PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app + run: | + cd dist/mas-dev-universal + zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip - path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip + path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip if-no-files-found: error diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 46f4ffad57d..0013234faa3 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -393,10 +393,7 @@ jobs: macos-build: name: MacOS Build - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: setup env: _PACKAGE_VERSION: ${{ needs.setup.outputs.release-version }} @@ -457,7 +454,7 @@ jobs: az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 - + az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 @@ -525,10 +522,7 @@ jobs: macos-package-github: name: MacOS Package GitHub Release Assets - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: - setup - macos-build @@ -596,7 +590,7 @@ jobs: az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 - + az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 @@ -665,7 +659,7 @@ jobs: - name: Download artifact from hotfix-rc if: github.ref == 'refs/heads/hotfix-rc' - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success @@ -674,7 +668,7 @@ jobs: - name: Download artifact from rc if: github.ref == 'refs/heads/rc' - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success @@ -683,7 +677,7 @@ jobs: - name: Download artifacts from main if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }} - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success @@ -738,10 +732,7 @@ jobs: macos-package-mas: name: MacOS Package Prod Release Asset - # Note, this workflow is running on macOS 11 to maintain compatibility with macOS 10.15 Catalina, - # as the newer versions will case the native modules to be incompatible with older macOS systems - # This version should stay pinned until we drop support for macOS 10.15, or we drop the native modules - runs-on: macos-11 + runs-on: macos-13 needs: - setup - macos-build @@ -804,7 +795,7 @@ jobs: az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/bitwarden-desktop-key | jq -r .value | base64 -d > $HOME/certificates/bitwarden-desktop-key.p12 - + az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/appstore-app-cert | jq -r .value | base64 -d > $HOME/certificates/appstore-app-cert.p12 @@ -873,7 +864,7 @@ jobs: - name: Download artifact from hotfix-rc if: github.ref == 'refs/heads/hotfix-rc' - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success @@ -882,7 +873,7 @@ jobs: - name: Download artifact from rc if: github.ref == 'refs/heads/rc' - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success @@ -891,7 +882,7 @@ jobs: - name: Download artifact from main if: ${{ github.ref != 'refs/heads/rc' && github.ref != 'refs/heads/hotfix-rc' }} - uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 + uses: bitwarden/gh-actions/download-artifacts@main with: workflow: build-browser.yml workflow_conclusion: success diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3c650d8a622..12649b91ea9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,10 +48,12 @@ jobs: # Tests in apps/ are typechecked when their app is built, so we just do it here for libs/ # See https://bitwarden.atlassian.net/browse/EC-497 - name: Run typechecking - run: npm run test:types --coverage + run: npm run test:types - name: Run tests - run: npm run test --coverage + # maxWorkers is a workaround for a memory leak that crashes tests in CI: + # https://github.com/facebook/jest/issues/9430#issuecomment-1149882002 + run: npm test -- --coverage --maxWorkers=3 - name: Report test results uses: dorny/test-reporter@eaa763f6ffc21c7a37837f56cd5f9737f27fc6c8 # v1.8.0 diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml index 15041aaa5d8..4bf502da21c 100644 --- a/.github/workflows/version-bump.yml +++ b/.github/workflows/version-bump.yml @@ -28,6 +28,10 @@ on: description: "Cut RC branch?" default: true type: boolean + enable_slack_notification: + description: "Enable Slack notifications for upcoming release?" + default: false + type: boolean jobs: bump_version: @@ -45,6 +49,14 @@ jobs: with: version: ${{ inputs.version_number_override }} + - name: Slack Notification Check + run: | + if [[ "${{ inputs.enable_slack_notification }}" == true ]]; then + echo "Slack notifications enabled." + else + echo "Slack notifications disabled." + fi + - name: Checkout Branch uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: @@ -476,7 +488,7 @@ jobs: run: gh pr merge $PR_NUMBER --squash --auto --delete-branch - name: Report upcoming browser release version to Slack - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_browser != '' }} + if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_browser != '' && inputs.enable_slack_notification == true }} uses: bitwarden/gh-actions/report-upcoming-release-version@main with: version: ${{ steps.set-final-version-output.outputs.version_browser }} @@ -484,7 +496,7 @@ jobs: AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Report upcoming cli release version to Slack - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_cli != '' }} + if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_cli != '' && inputs.enable_slack_notification == true }} uses: bitwarden/gh-actions/report-upcoming-release-version@main with: version: ${{ steps.set-final-version-output.outputs.version_cli }} @@ -492,7 +504,7 @@ jobs: AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Report upcoming desktop release version to Slack - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_desktop != '' }} + if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_desktop != '' && inputs.enable_slack_notification == true }} uses: bitwarden/gh-actions/report-upcoming-release-version@main with: version: ${{ steps.set-final-version-output.outputs.version_desktop }} @@ -500,7 +512,7 @@ jobs: AZURE_KV_CI_SERVICE_PRINCIPAL: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} - name: Report upcoming web release version to Slack - if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_web != '' }} + if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' && steps.set-final-version-output.outputs.version_web != '' && inputs.enable_slack_notification == true }} uses: bitwarden/gh-actions/report-upcoming-release-version@main with: version: ${{ steps.set-final-version-output.outputs.version_web }} diff --git a/.nvmrc b/.nvmrc index 3f430af82b3..9a2a0e219c9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v20 diff --git a/apps/browser/package.json b/apps/browser/package.json index 1f54bd64aca..a295a0f5bfe 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2024.5.0", + "version": "2024.6.0", "scripts": { "build": "cross-env MANIFEST_VERSION=3 webpack", "build:mv2": "webpack", diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index fc61b8010f3..b689a7e3fc0 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "المفضلات" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "الملاحظات" }, @@ -404,6 +419,9 @@ "launch": { "message": "بدء" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "الموقع الإلكتروني" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "تم تسجيل الخروج" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "انتهت صلاحية جلسة تسجيل الدخول الخاصة بك." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "إلغاء القفل" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "إظهار خيارات قائمة السياق" }, @@ -793,12 +817,39 @@ "message": "داكن مُشمس", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "تصدير الخزنة" }, "fileFormat": { "message": "صيغة الملف" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "تحذير", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "حدد عنوان URL الأساسي لتثبيت Bitwarden المستضاف محليًا." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "بيئة مخصصة" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "المجموعات" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "المفضلات" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "تم استعادة العنصر" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "سيؤدي تسجيل الخروج إلى إزالة جميع إمكانية الوصول إلى خزنتك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "التعبئة التلقائية والحفظ" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "تم تعبئة العنصر تلقائياً وحفظ عنوان URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "موافق" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "التحقق من مزامنة سطح المكتب" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "خطأ" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "الاتحاد الأوروبي", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 8b65adc90a8..4ded914a439 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -173,13 +173,19 @@ "message": "Veb tətbiqlə davam edilsin?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Veb tətbiqində Bitwarden hesabınızın daha çox özəlliyini kəşf edin." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Kömək Mərkəzi ilə davam edilsin?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Kömək Mərkəzində Bitwarden-in necə istifadə ediləcəyi ilə bağlı ətraflı öyrənin." + }, + "continueToBrowserExtensionStore": { + "message": "Brauzer uzantısını mağazasına gedirsiniz?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Başqalarının Bitwarden-in onlar üçün uyğun olub-olmadığını öyrənməkdə kömək edin. Brauzerin uzantı mağazasını ziyarət edin və qiymətləndirin." }, "changeMasterPasswordOnWebConfirmation": { "message": "Ana parolunuzu Bitwarden veb tətbiqində dəyişdirə bilərsiniz." @@ -199,43 +205,43 @@ "message": "Çıxış" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Bitwarden haqqında" }, "about": { "message": "Haqqında" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "\"Bitwarden\"dən daha çoxu" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "bitwarden.com ilə davam edilsin?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Biznes üçün Bitwarden" }, "bitwardenAuthenticator": { "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 açarlarını saxlamağınıza və 2 addımlı doğrulama axınları üçün TOTP kodlarını yaratmağınıza imkan verir. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden Sirr Meneceri" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Bitwarden Sirr Meneceri ilə developer sirlərini güvənli bir şəkildə saxlayın, idarə edin və paylaşın. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." }, "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 ilə ənənəvi parollardan azad, problemsiz və güvənli giriş təcrübələri yaradın. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Ödənişsiz Bitwarden Ailələri" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Ödənişsiz Bitwarden Ailələri üçün uyğunsunuz. Bu təklifdən veb tətbiqdən yararlanın." }, "version": { "message": "Versiya" @@ -296,7 +302,7 @@ "message": "Hesablarınız üçün avtomatik olaraq güclü, unikal parollar yaradın." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden veb tətbiqi" }, "importItems": { "message": "Elementləri daxilə köçür" @@ -383,6 +389,15 @@ "favorite": { "message": "Sevimli" }, + "unfavorite": { + "message": "Sevimlilərdən sil" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notlar" }, @@ -404,6 +419,9 @@ "launch": { "message": "Başlat" }, + "launchWebsite": { + "message": "Veb saytı başlat" + }, "website": { "message": "Veb sayt" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Çıxış edildi" }, + "loggedOutDesc": { + "message": "Hesabınızdan çıxış etmisiniz." + }, "loginExpired": { "message": "Giriş seansınızın müddəti bitdi." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Kilidi aç" }, + "additionalOptions": { + "message": "Əlavə seçimlər" + }, "enableContextMenuItem": { "message": "Konteks menyu seçimlərini göstər" }, @@ -793,12 +817,39 @@ "message": "Günəşli tünd", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Buradan xaricə köçür" + }, "exportVault": { "message": "Anbarı xaricə köçür" }, "fileFormat": { "message": "Fayl formatı" }, + "fileEncryptedExportWarningDesc": { + "message": "Bu faylın xaricə köçürülməsi, parolla qorunacaq və şifrəsini açmaq üçün fayl parolu tələb olunacaq." + }, + "filePassword": { + "message": "Fayl parolu" + }, + "exportPasswordDescription": { + "message": "Bu parol, bu faylı daxilə və xaricə köçürmək üçün istifadə olunacaq" + }, + "accountRestrictedOptionDescription": { + "message": "Xaricə köçürməni şifrələmək və daxilə köçürməni yalnız mövcud Bitwarden hesabı ilə məhdudlaşdırmaq üçün hesabınızın istifadəçi adı və Ana Parolundan əldə edilən hesab şifrələmə açarınızı istifadə edin." + }, + "passwordProtectedOptionDescription": { + "message": "Xaricə köçürməni şifrələmək üçün bir fayl parolu təyin edin və şifrəni açma parolunu istifadə edərək bunu istənilən Bitwarden hesabına köçürün." + }, + "exportTypeHeading": { + "message": "Xaricə köçürmə növü" + }, + "accountRestricted": { + "message": "Hesab məhdudlaşdırıldı" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Fayl parolu\" və \"Fayl parolunu təsdiqlə\" uyuşmur." + }, "warning": { "message": "XƏBƏRDARLIQ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Paylaşılan" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business, bir təşkilat hesabı istifadə edərək anbar elementlərinizi başqaları ilə paylaşmağınıza imkan verir. Daha ətraflı bitwarden.com veb saytında öyrənə bilərsiniz." }, "moveToOrganization": { "message": "Təşkilata daşı" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının baza URL-sini müəyyənləşdirin." }, + "selfHostedBaseUrlHint": { + "message": "Şirkət daxili sahiblik edən Bitwarden quraşdırmasının təməl URL-sini qeyd edin. Nümunə: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Qabaqcıl konfiqurasiya üçün hər xidmətin təməl URL-sini müstəqil olaraq qeyd edə bilərsiniz." + }, + "selfHostedEnvFormInvalid": { + "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." + }, "customEnvironment": { "message": "Özəl mühit" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kolleksiyalar" }, + "nCollections": { + "message": "$COUNT$ kolleksiya", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Sevimlilər" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Element bərpa edildi" }, + "alreadyHaveAccount": { + "message": "Artıq hesabınız var?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Çıxış etdikdə anbarınıza bütün müraciətiniz dayanacaq və vaxt bitməsindən sonra onlayn kimlik doğrulaması tələb olunacaq. Bu ayarı istifadə etmək istədiyinizə əminsiniz?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Avto-doldur və saxla" }, + "fillAndSave": { + "message": "Doldur və saxla" + }, "autoFillSuccessAndSavedUri": { "message": "Element avto-dolduruldu və URI saxlanıldı" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Oldu" }, + "errorRefreshingAccessToken": { + "message": "Müraciət tokeni təzələmə xətası" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Təzələmə tokeni və ya API açarlar tapılmadı. Lütfən çıxış edib yenidən giriş etməyə çalışın." + }, "desktopSyncVerificationTitle": { "message": "Masaüstü sinxr doğrulaması" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Təşkilat anbarını xaricə köçürmə" + }, + "exportingOrganizationVaultDesc": { + "message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat anbarı ixrac ediləcək. Fərdi anbardakı və digər təşkilat elementlər daxil edilmir.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Xəta" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Təşkilat SSO identifikatoru tələb olunur." }, + "creatingAccountOn": { + "message": "Hesab yaradılır" + }, + "checkYourEmail": { + "message": "E-poçtunuzu yoxlayın" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Göndərilən e-poçtdakı keçidi izləyin" + }, + "andContinueCreatingYourAccount": { + "message": "və hesabınızı yaratmağa davam edin." + }, + "noEmail": { + "message": "E-poçt yoxdur?" + }, + "goBack": { + "message": "Geri qayıt" + }, + "toEditYourEmailAddress": { + "message": "və e-poçt ünvanına düzəliş et." + }, "eu": { "message": "AB", "description": "European Union" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "Filtrləri təmizləyin və ya başqa bir axtarış terminini sınayın" }, - "copyInfo": { - "message": "Məlumatları kopyala, $ITEMNAME$", + "copyInfoLabel": { + "message": "$ITEMNAME$ elementlərini kopyala", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Məlumatları kopyala - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "$ITEMNAME$ notunu kopyala", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Notu kopyala - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Daha çox seçim, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Daha çox seçim - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "$ITEMNAME$ elementinə bax", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Kolleksiyaları təyin et" + }, + "copyEmail": { + "message": "E-poçtu kopyala" + }, + "copyPhone": { + "message": "Telefonu kopyala" + }, + "copyAddress": { + "message": "Ünvanı kopyala" + }, "adminConsole": { "message": "Admin Konsolu" }, @@ -3230,7 +3406,7 @@ "message": "Hədəf qovluğa təyin etmə xətası." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ daxilindəki elementlərə bax", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Geri qayıt: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Yeni" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Sil: $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Qovluğu olmayan elementlər" + }, + "organizationIsDeactivated": { + "message": "Təşkilat deaktiv edildi" + }, + "contactYourOrgAdmin": { + "message": "Deaktiv edilmiş təşkilatlardakı elementlərə müraciət edilə bilməz. Kömək üçün təşkilatınızın sahibi ilə əlaqə saxlayın." } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 4e7be1f47fc..22aa6e1fa33 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Абраны" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Нататкі" }, @@ -404,6 +419,9 @@ "launch": { "message": "Запусціць" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Вэб-сайт" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Вы выйшлі" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Разблакіраваць" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Паказваць параметры кантэкстнага меню" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Экспартаваць сховішча" }, "fileFormat": { "message": "Фармат файла" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ПАПЯРЭДЖАННЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Увядзіце асноўны URL-адрас вашага лакальнага размяшчэння ўсталяванага Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Калекцыі" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Абраныя" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Элемент адноўлены" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Выхад з сістэмы скасуе ўсе магчымасці доступу да сховішча і запатрабуе аўтэнтыфікацыю праз інтэрнэт пасля завяршэння часу чакання. Вы сапраўды хочаце выкарыстоўваць гэты параметр?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Аўтазапоўніць і захаваць" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Аўтазапоўнены элемент і захаваны URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Добра" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Праверка сінхранізацыі на камп'ютары" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Памылка" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "ЕС", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 9b3add9ad5f..c5cf062b344 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Научете повече относно това как да използвате Битуорден в помощния център." }, + "continueToBrowserExtensionStore": { + "message": "Продължаване към уеб сайта за разширения на браузъра?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Помогнете на другите да разберат дали Битуорден е подходящ за тях. Посетете уеб сайта за разширения на браузъра си и дайте оценка още сега." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Може да промените главната си парола в уеб приложението на Битуорден." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Любими" }, + "unfavorite": { + "message": "Изваждане от любими" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Бележки" }, @@ -404,6 +419,9 @@ "launch": { "message": "Пускане" }, + "launchWebsite": { + "message": "Посещаване на уеб сайта" + }, "website": { "message": "Сайт" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Бяхте отписани" }, + "loggedOutDesc": { + "message": "Бяхте отписан(а) от регистрацията си." + }, "loginExpired": { "message": "Сесията ви изтече." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Отключване" }, + "additionalOptions": { + "message": "Допълнителни настройки" + }, "enableContextMenuItem": { "message": "Показване на опции в контекстното меню" }, @@ -793,12 +817,39 @@ "message": "Преекспонирано тъмен", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Изнасяне от" + }, "exportVault": { "message": "Изнасяне на трезора" }, "fileFormat": { "message": "Формат на файла" }, + "fileEncryptedExportWarningDesc": { + "message": "Изнесеният файл ще бъде защитен с парола, която ще бъде необходима за дешифриране на файла." + }, + "filePassword": { + "message": "Парола на файла" + }, + "exportPasswordDescription": { + "message": "Парола ще се използва при изнасянето и при внасянето на този файл" + }, + "accountRestrictedOptionDescription": { + "message": "Използвайте ключа си за шифриране, който се получава чрез комбиниране на потребителското име на регистрацията Ви и главната парола. С него изнасянето ще се шифрира и внасянето ще бъда възможно само в текущата регистрация в Битуорден." + }, + "passwordProtectedOptionDescription": { + "message": "Задайте парола за файла, за да шифровате изнесените данни. Ще можете да внесете данните във всяка регистрация в Битуорден използвайки паролата за дешифриране." + }, + "exportTypeHeading": { + "message": "Вид изнасяне" + }, + "accountRestricted": { + "message": "Регистрацията е ограничена" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Дънните в полетата „Парола на файла“ и „Потвърждаване на паролата на файла“ не съвпадат." + }, "warning": { "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Укажете базовия адрес за собствената ви инсталирана среда на Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Посочете базовия адрес на Вашата собствена инсталация на Битуорден. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За по-детайлна настройка, може да укажете основния адрес на всяка услуга поотделно." + }, + "selfHostedEnvFormInvalid": { + "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." + }, "customEnvironment": { "message": "Специална среда" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Колекции" }, + "nCollections": { + "message": "$COUNT$ колекции", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Любими" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Записът е възстановен" }, + "alreadyHaveAccount": { + "message": "Вече имате регистрация?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Излизането от трезора изцяло спира достъпа до него след изтичане на времето. Ще ви се наложи отново да се идентифицирате, за да го достъпите. Сигурни ли сте, че искате това действие?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Дописване и обновяване" }, + "fillAndSave": { + "message": "Попълване и запазване" + }, "autoFillSuccessAndSavedUri": { "message": "Автоматично дописан запис и адрес" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Добре" }, + "errorRefreshingAccessToken": { + "message": "Грешка при опресняването на идентификатора за достъп" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Няма намерен идентификатор за опресняване или ключове за ППИ. Опитайте да се отпишете и да се впишете отново." + }, "desktopSyncVerificationTitle": { "message": "Потвърждаване на синхронизацията на самостоятелното приложение" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Изнасяне на трезора на организацията" + }, + "exportingOrganizationVaultDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$. Записите в отделните лични трезори и тези в други организации няма да бъдат включени.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Грешка" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Идентификаторът за еднократна идентификация на организация е задължителен." }, + "creatingAccountOn": { + "message": "Създаване на регистрация в" + }, + "checkYourEmail": { + "message": "Проверете е-пощата си" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Последвайте връзката в е-писмото изпратено до" + }, + "andContinueCreatingYourAccount": { + "message": "и продължете със създаването на регистрацията си." + }, + "noEmail": { + "message": "Не сте получили е-писмо?" + }, + "goBack": { + "message": "Върнете се назад" + }, + "toEditYourEmailAddress": { + "message": ", за да редактирате адреса на е-пощата си." + }, "eu": { "message": "ЕС", "description": "European Union" @@ -3093,7 +3207,7 @@ "message": "сървър" }, "hostedAt": { - "message": "hosted at" + "message": "домакинствано при" }, "useDeviceOrHardwareKey": { "message": "Използвайте своето устройство или хардуерен ключ" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Изчистете филтрите или опитайте да търсите нещо друго" }, - "copyInfo": { + "copyInfoLabel": { "message": "Копиране на информацията, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Копиране на информацията – $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Копиране на бележката, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Копиране на бележката – $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Още опции, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Още опции – $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Преглед на елемента – $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Свързване на колекции" + }, + "copyEmail": { + "message": "Копиране на е-пощата" + }, + "copyPhone": { + "message": "Копиране на телефона" + }, + "copyAddress": { + "message": "Копиране на адреса" + }, "adminConsole": { "message": "Административна конзола" }, @@ -3230,7 +3406,7 @@ "message": "Грешка при задаването на целева папка." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Преглед на елементите в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад към $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Ново" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Премахване на $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Елементи без папка" + }, + "organizationIsDeactivated": { + "message": "Организацията е деактивирана" + }, + "contactYourOrgAdmin": { + "message": "Записите в деактивирани организации не са достъпни. Свържете се със собственика на организацията си за помощ." } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 41fcdb980a3..3ddf7a32bde 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "প্রিয়" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "নোট" }, @@ -404,6 +419,9 @@ "launch": { "message": "শুরু" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "ওয়েবসাইট" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "প্রস্থানকৃত" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "আপনার লগইন মাত্রকালটির মেয়াদ শেষ হয়ে গেছে।" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "ভল্ট রফতানি" }, "fileFormat": { "message": "ফাইলের ধরণ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "সতর্কতা", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "আপনার অন-প্রাঙ্গনে হোস্টকৃত Bitwarden ইনস্টলেশনটির বেস URL উল্লেখ করুন।" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "সংগ্রহ" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "প্রিয়গুলো" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "বস্তু পুনরুদ্ধারকৃত" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "লগ আউট করা আপনার ভল্টের সমস্ত অ্যাক্সেস সরিয়ে ফেলবে এবং সময়সীমার পরে অনলাইন প্রমাণীকরণের প্রয়োজন। আপনি কি নিশ্চিত যে এই সেটিংটি ব্যবহার করবেন?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "স্বতঃপূরণ ও সংরক্ষণ" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "স্বতঃপূরণকৃত বস্তু ও সংরক্ষিত URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "ঠিক আছে" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "ডেস্কটপ সিঙ্ক যাচাইকরণ" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 7b85e5c3619..f8d275245ea 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index cac7ecc1520..aee61f4b1ad 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Podeu canviar la vostra contrasenya mestra a l'aplicació web de Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Preferit" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Inicia" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Lloc web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Sessió tancada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "La vostra sessió ha caducat." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Desbloqueja" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Mostra les opcions del menú contextual" }, @@ -793,12 +817,39 @@ "message": "Solaritzat fosc", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exporta caixa forta" }, "fileFormat": { "message": "Format de fitxer" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ADVERTIMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifiqueu l'URL base de la vostra instal·lació de Bitwarden allotjada en un entorn propi." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Entorn personalitzat" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Col·leccions" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Preferits" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Element restaurat" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "En tancar la sessió s'eliminarà tot l'accés a la vostra caixa forta i es requerirà una autenticació en línia després del període de temps d'espera. Esteu segur que voleu utilitzar aquesta configuració?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Ompli automàticament i guarda" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Element emplenat automàticament i URI guardat" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "D’acord" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verificació de sincronització d'escriptori" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Es requereix un identificador SSO de l'organització." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "UE", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Consola d'administració" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 12cfcf1fd74..a865a1a9472 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Přečtěte si více o tom, jak používat Bitwarden v Centru nápovědy." }, + "continueToBrowserExtensionStore": { + "message": "Pokračovat do obchdou s rozšířeními pro prohlížeč?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Pomozte ostatním zjistit, zda je Bitwarden pro ně správný. Navštivte obchod s rozšířeními pro prohlížeč a zanechte hodnocení." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Hlavní heslo můžete změnit ve webové aplikaci Bitwardenu." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Oblíbené" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Poznámky" }, @@ -404,6 +419,9 @@ "launch": { "message": "Spustit" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Webová stránka" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Odhlášení" }, + "loggedOutDesc": { + "message": "Byli jste odhlášeni ze svého účtu." + }, "loginExpired": { "message": "Platnost přihlášení vypršela." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Odemknout" }, + "additionalOptions": { + "message": "Další volby" + }, "enableContextMenuItem": { "message": "Zobrazit volby v kontextovém menu" }, @@ -793,12 +817,39 @@ "message": "Tmavý (solarizovaný)", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportovat z" + }, "exportVault": { "message": "Exportovat trezor" }, "fileFormat": { "message": "Formát souboru" }, + "fileEncryptedExportWarningDesc": { + "message": "Tento soubor exportu bude chráněn heslem a k dešifrování bude vyžadovat heslo souboru." + }, + "filePassword": { + "message": "Heslo souboru" + }, + "exportPasswordDescription": { + "message": "Toto heslo bude použito pro export a import tohoto souboru" + }, + "accountRestrictedOptionDescription": { + "message": "Pro zašifrování exportu a omezení importu pouze na aktuální účet Bitwardenu použijte šifrovací klíč Vašeho účtu odvozený z uživatelského jména a hlavního hesla." + }, + "passwordProtectedOptionDescription": { + "message": "Nastavte heslo pro šifrování exportu a importujte ho do libovolného účtu Bitwardenu pomocí hesla pro dešifrování." + }, + "exportTypeHeading": { + "message": "Typ exportu" + }, + "accountRestricted": { + "message": "Účet je omezený" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Heslo souboru\" a \"Potvrzení hesla souboru\" se neshodují." + }, "warning": { "message": "VAROVÁNÍ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Zadejte základní URL adresu vlastní hostované aplikace Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Zadejte základní URL adresu Vaší vlastní hostované aplikace Bitwarden. Příklad: https://bitwarden.spolecnost.cz" + }, + "selfHostedCustomEnvHeader": { + "message": "Pro rozšířená nastavení můžete zadat základní URL adresu každé služby zvlášť." + }, + "selfHostedEnvFormInvalid": { + "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." + }, "customEnvironment": { "message": "Vlastní prostředí" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kolekce" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Oblíbené" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Položka byla obnovena" }, + "alreadyHaveAccount": { + "message": "Už máte účet?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Po vypršení časového limitu dojde k odhlášení. Přístup k trezoru bude odebrán a pro opětovné přihlášení bude vyžadováno online ověření. Opravdu chcete použít toto nastavení?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automaticky vyplnit a uložit" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Položka byla automaticky vyplněna a URI bylo uloženo" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "OK" }, + "errorRefreshingAccessToken": { + "message": "Chyba aktualizace přístupového tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nebyly nalezeny žádné obnovovací tokeny nebo API klíče. Zkuste se odhlásit a znovu se přihlásit." + }, "desktopSyncVerificationTitle": { "message": "Ověření synchronizace s aplikací pro počítač" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exportování trezoru organizace" + }, + "exportingOrganizationVaultDesc": { + "message": "Exportován bude jen trezor organizace přidružený k položce $ORGANIZATION$. Osobní položky trezoru a položky z jiných organizací nebudou zahrnuty.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Chyba" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Je vyžadován SSO identifikátor organizace." }, + "creatingAccountOn": { + "message": "Vytváření účtu na" + }, + "checkYourEmail": { + "message": "Zkontrolujte Váš e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Následujte pokyny v e-mailu poslaném na" + }, + "andContinueCreatingYourAccount": { + "message": "a pokračujte ve vytváření Vašeho účtu." + }, + "noEmail": { + "message": "Žádný e-mail?" + }, + "goBack": { + "message": "Zpět" + }, + "toEditYourEmailAddress": { + "message": "pro úpravu Vaší e-mailové adresy." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Vymažte filtry nebo zkuste jiný hledaný výraz" }, - "copyInfo": { + "copyInfoLabel": { "message": "Kopírovat informace, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Kopírovat informace - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Kopírovat poznámku, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Kopírovat poznámku - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Více voleb, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Více voleb - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Zobrazit položku - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Kopírovat e-mail" + }, + "copyPhone": { + "message": "Kopírovat telefon" + }, + "copyAddress": { + "message": "Kopírovat adresu" + }, "adminConsole": { "message": "Konzole správce" }, @@ -3230,7 +3406,7 @@ "message": "Chyba při přiřazování cílové složky." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobrazit položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zpět do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Nové" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odebrat $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Položky bez složky" + }, + "organizationIsDeactivated": { + "message": "Organizace je deaktivována" + }, + "contactYourOrgAdmin": { + "message": "K položkám v deaktivované organizaci nemáte přístup. Požádejte o pomoc vlastníka organizace." } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 664a2f83f02..f90120a0823 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Ffefrynnu" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Nodiadau" }, @@ -404,6 +419,9 @@ "launch": { "message": "Lansio" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Gwefan" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Mae eich sesiwn wedi dod i ben." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Datgloi" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Allforio'r gell" }, "fileFormat": { "message": "Fformat y ffeil" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "RHYBUDD", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Amgylchedd addasedig" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Casgliadau" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Ffefrynnau" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Llenwi'n awtomatig a chadw" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Gwall" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "UE", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index fbee1390d24..7b5b7ec2e29 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Læs mere i Hjælpecenter om, hvordan man bruger Bitwarden." }, + "continueToBrowserExtensionStore": { + "message": "Fortsæt til webbrowserudvidelsesbutik?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Hjælp andre med at finde ud af, om Bitwarden er det rigtige for dem. Besøg App Store og skriv en bedømmelse nu." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Hovedadgangskoden kan ændres via Bitwarden web-appen." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorit" }, + "unfavorite": { + "message": "Fjern fra favorit" + }, + "itemAddedToFavorites": { + "message": "Emne føjet til favoritter" + }, + "itemRemovedFromFavorites": { + "message": "Emne fjernet fra favoritter" + }, "notes": { "message": "Notater" }, @@ -404,6 +419,9 @@ "launch": { "message": "Start" }, + "launchWebsite": { + "message": "Åbn websted" + }, "website": { "message": "Hjemmeside" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logget ud" }, + "loggedOutDesc": { + "message": "Der er blevet logget ud af kontoen." + }, "loginExpired": { "message": "Din login-session er udløbet." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Oplås" }, + "additionalOptions": { + "message": "Yderligere indstillinger" + }, "enableContextMenuItem": { "message": "Vis indstillinger i kontekstmenuen" }, @@ -793,12 +817,39 @@ "message": "Solariseret mørk", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Eksportér fra" + }, "exportVault": { "message": "Eksportér boks" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "Denne fileksport vil være adgangskodebeskyttet og kræve filadgangskoden at dekryptere." + }, + "filePassword": { + "message": "Filadgangskode" + }, + "exportPasswordDescription": { + "message": "Denne adgangskode vil blive brugt ved eksport og import af denne fil" + }, + "accountRestrictedOptionDescription": { + "message": "Brug kontokrypteringsnøglen, dannet af kontobrugernavn og Hovedadgangskode, for at kryptere eksporten og hindre import til andre end den aktuelle Bitwarden-konto." + }, + "passwordProtectedOptionDescription": { + "message": "Opsæt en adgangskode til både at kryptere eksporten samt dekryptere denne ved import til enhver Bitwarden-konto." + }, + "exportTypeHeading": { + "message": "Eksporttype" + }, + "accountRestricted": { + "message": "Konto begrænset" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“Filadgangskode” og “Bekræft filadgangskode“ matcher ikke." + }, "warning": { "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Angiv grund-URL'en i din lokal-hostede Bitwarden-installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Brugerdefineret miljø" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Samlinger" }, + "nCollections": { + "message": "$COUNT$ samlinger", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritter" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Element gendannet" }, + "alreadyHaveAccount": { + "message": "Har allerede en konto?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Ved at logge ud fjernes al adgang til din boks og kræver online-godkendelse efter timeout-perioden. Er du sikker på, at du vil bruge denne indstilling?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Autoudfyld og gem" }, + "fillAndSave": { + "message": "Udfyld og gem" + }, "autoFillSuccessAndSavedUri": { "message": "Autoudfyldte element og URI gemt" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Adgangstoken genopfriskningsfejl" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Ingen genopfriskningstoken eller API-nøgler fundet. Prøv at logge ud og dernæst ind igen." + }, "desktopSyncVerificationTitle": { "message": "Verifikation af skrivebordssynkronisering" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Eksport af organisationsboks" + }, + "exportingOrganizationVaultDesc": { + "message": "Kun organisationsboksen tilknyttet $ORGANIZATION$ eksporteres. Emner i individuelle bokse eller andre organisationer medtages ikke.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Fejl" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organisations SSO-identifikator kræves." }, + "creatingAccountOn": { + "message": "Opretter konto på" + }, + "checkYourEmail": { + "message": "Tjek din e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Følg linket i e-mailen sendt til" + }, + "andContinueCreatingYourAccount": { + "message": "og fortsæt med kontooprettelsen." + }, + "noEmail": { + "message": "Ingen e-mail?" + }, + "goBack": { + "message": "Gå tilbage" + }, + "toEditYourEmailAddress": { + "message": "for at redigere e-mailadressen." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Ryd filtre eller prøv med et andet søgeord" }, - "copyInfo": { + "copyInfoLabel": { "message": "Kopiér info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Kopiér info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Kopiér notat, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Kopiér notat - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Flere valgmuligheder, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Flere valgmuligheder - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Vis emne - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Tildel samlinger" + }, + "copyEmail": { + "message": "Kopiér e-mail" + }, + "copyPhone": { + "message": "Kopiér telefon" + }, + "copyAddress": { + "message": "Kopiér addresse" + }, "adminConsole": { "message": "Admin-konsol" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Emner uden mappe" + }, + "organizationIsDeactivated": { + "message": "Organisation er deaktiveret" + }, + "contactYourOrgAdmin": { + "message": "Emner i deaktiverede organisationer kan ikke tilgås. Kontakt organisationsejeren for assistance." } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index a204822e3b4..dddc6fc9762 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Erfahre mehr über die Verwendung von Bitwarden im Hilfezentrum." }, + "continueToBrowserExtensionStore": { + "message": "Weiter zum Store für Browser-Erweiterungen?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Hilf anderen herauszufinden, ob Bitwarden für sie das Richtige ist. Besuch den Erweiterungs-Store deines Browsers und hinterlasse jetzt eine Bewertung." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Du kannst dein Master-Passwort in der Bitwarden Web-App ändern." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favoriten" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notizen" }, @@ -404,6 +419,9 @@ "launch": { "message": "Öffnen" }, + "launchWebsite": { + "message": "Website öffnen" + }, "website": { "message": "Webseite" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Ausgeloggt" }, + "loggedOutDesc": { + "message": "Du wurdest von deinem Konto abgemeldet." + }, "loginExpired": { "message": "Ihre Login-Sitzung ist abgelaufen." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Entsperren" }, + "additionalOptions": { + "message": "Weitere Optionen" + }, "enableContextMenuItem": { "message": "Kontextmenüoptionen anzeigen" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export aus" + }, "exportVault": { "message": "Tresor exportieren" }, "fileFormat": { "message": "Dateiformat" }, + "fileEncryptedExportWarningDesc": { + "message": "Dieser Datei-Export ist passwortgeschützt und erfordert das Dateipasswort zum Entschlüsseln." + }, + "filePassword": { + "message": "Dateipasswort" + }, + "exportPasswordDescription": { + "message": "Dieses Passwort wird zum Exportieren und Importieren dieser Datei verwendet" + }, + "accountRestrictedOptionDescription": { + "message": "Verwende den Verschlüsselungsschlüssel deines Kontos, abgeleitet vom Benutzernamen und Master-Passwort, um den Export zu verschlüsseln und den Import auf das aktuelle Bitwarden-Konto zu beschränken." + }, + "passwordProtectedOptionDescription": { + "message": "Lege ein Dateipasswort fest, um den Export zu verschlüsseln und importiere ihn in ein beliebiges Bitwarden-Konto, wobei das Passwort zum Entschlüsseln genutzt wird." + }, + "exportTypeHeading": { + "message": "Exporttyp" + }, + "accountRestricted": { + "message": "Konto eingeschränkt" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "„Dateipasswort“ und „Dateipasswort bestätigen“ stimmen nicht überein." + }, "warning": { "message": "ACHTUNG", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Bitte gib die Basis-URL deiner selbst gehosteten Bitwarden-Installation an." }, + "selfHostedBaseUrlHint": { + "message": "Gib die Basis-URL deiner vor Ort gehosteten Bitwarden-Installation an. Beispiel: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Für eine erweiterte Konfiguration kannst du die Basis-URL jedes Dienstes unabhängig voneinander angeben." + }, + "selfHostedEnvFormInvalid": { + "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." + }, "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Sammlungen" }, + "nCollections": { + "message": "$COUNT$ Sammlungen", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoriten" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Eintrag wiederhergestellt" }, + "alreadyHaveAccount": { + "message": "Hast du bereits ein Konto?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Nach dem Ausloggen verlierest du jeglichen Zugriff auf deinen Tresor und es ist nach Ablauf der Timeout-Zeit eine Online-Authentifizierung erforderlich. Bist du sicher, dass du diese Einstellung nutzen möchten?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-Ausfüllen und speichern" }, + "fillAndSave": { + "message": "Ausfüllen und speichern" + }, "autoFillSuccessAndSavedUri": { "message": "Eintrag automatisch ausgefüllt und URI gespeichert" }, @@ -1688,7 +1763,7 @@ "message": "Ihr neues Master-Passwort entspricht nicht den Anforderungen der Richtlinie." }, "acceptPolicies": { - "message": "Durch Anwählen dieses Kästchens erklären Sie sich mit folgendem einverstanden:" + "message": "Durch Anwählen dieses Kästchens erklärst du dich mit Folgendem einverstanden:" }, "acceptPoliciesRequired": { "message": "Die Nutzungsbedingungen und Datenschutzerklärung wurden nicht akzeptiert." @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Zugangs-Token Aktualisierungsfehler" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Kein Aktualisierungs-Token oder API-Schlüssel gefunden. Bitte versuche dich ab- und wieder anzumelden." + }, "desktopSyncVerificationTitle": { "message": "Desktop-Sync-Überprüfung" }, @@ -1757,7 +1838,7 @@ "message": "Nutzer gesperrt oder abgemeldet" }, "biometricsNotUnlockedDesc": { - "message": "Bitte entsperren Sie diesen Nutzer in der Desktop-Anwendung und versuchen Sie es erneut." + "message": "Bitte entsperre diesen Nutzer in der Desktop-Anwendung und versuche es erneut." }, "biometricsFailedTitle": { "message": "Biometrie fehlgeschlagen" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Tresor der Organisation wird exportiert" + }, + "exportingOrganizationVaultDesc": { + "message": "Nur der mit $ORGANIZATION$ verbundene Organisationstresor wird exportiert. Einträge in persönlichen Tresoren oder anderen Organisationen werden nicht berücksichtigt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Fehler" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "SSO-Kennung der Organisation erforderlich." }, + "creatingAccountOn": { + "message": "Konto wird erstellt bei" + }, + "checkYourEmail": { + "message": "Überprüfe deine E-Mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Folge dem Link in der E-Mail an" + }, + "andContinueCreatingYourAccount": { + "message": "und fahre mit der Erstellung deines Kontos fort." + }, + "noEmail": { + "message": "Keine E-Mail?" + }, + "goBack": { + "message": "Geh zurück" + }, + "toEditYourEmailAddress": { + "message": ", um deine E-Mail-Adresse zu bearbeiten." + }, "eu": { "message": "EU", "description": "European Union" @@ -3183,7 +3297,7 @@ "message": "Speichere einen Login-Eintrag für diese Seite zum automatischen Ausfüllen" }, "yourVaultIsEmpty": { - "message": "Ihr Tresor hat keine Einträge" + "message": "Dein Tresor hat keine Einträge" }, "noItemsMatchSearch": { "message": "Deine Suche ergab keine Treffer" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "Entferne die Filter oder versuche einen anderen Suchbegriff" }, - "copyInfo": { - "message": "Information kopieren, $ITEMNAME$", + "copyInfoLabel": { + "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "Weitere Optionen, $ITEMNAME$", + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Sammlungen zuweisen" + }, + "copyEmail": { + "message": "E-Mail-Adresse kopieren" + }, + "copyPhone": { + "message": "Telefonnummer kopieren" + }, + "copyAddress": { + "message": "Adresse kopieren" + }, "adminConsole": { "message": "Administrator-Konsole" }, @@ -3230,7 +3406,7 @@ "message": "Fehler beim Zuweisen des Ziel-Ordners." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Einträge in $NAME$ anzeigen", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zurück zu $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Neu" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ entfernen", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Einträge ohne Ordner" + }, + "organizationIsDeactivated": { + "message": "Organisation ist deaktiviert" + }, + "contactYourOrgAdmin": { + "message": "Auf Einträge in deaktivierten Organisationen kann nicht zugegriffen werden. Kontaktiere deinen Organisationseigentümer für Unterstützung." } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 4453114be1e..1f97b3623c2 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Αγαπημένο" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Σημειώσεις" }, @@ -404,6 +419,9 @@ "launch": { "message": "Εκκίνηση" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Ιστοσελίδα" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Αποσυνδεθήκατε" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Η περίοδος σύνδεσης σας έχει λήξει." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Ξεκλείδωμα" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Εμφάνιση επιλογών μενού περιβάλλοντος" }, @@ -793,12 +817,39 @@ "message": "Solarized Σκούρο", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Εξαγωγή Vault" }, "fileFormat": { "message": "Μορφή αρχείου" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ΠΡΟΕΙΔΟΠΟΙΗΣΗ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Συλλογές" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Αγαπημένα" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Στοιχείο που έχει Ανακτηθεί" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Η αποσύνδεση θα καταργήσει όλη την πρόσβαση στο vault σας και απαιτεί online έλεγχο ταυτότητας μετά το χρονικό όριο λήξης. Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτήν τη ρύθμιση;" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Αυτόματη συμπλήρωση και αποθήκευση" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Αυτόματη συμπλήρωση στοιχείου και αποθηκευμένο URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Οκ" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Επιβεβαίωση συγχρονισμού επιφάνειας εργασίας" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Σφάλμα" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Απαιτείται αναγνωριστικό οργανισμού SSO." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "ΕΕ", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index bb672ea61d7..f9bf1bc523e 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -185,7 +185,7 @@ "message": "Continue to browser extension store?" }, "continueToBrowserExtensionStoreDesc": { - "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." @@ -224,7 +224,7 @@ }, "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" - }, + }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, @@ -389,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -410,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -599,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -762,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -799,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1077,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid" :{ + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1389,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1618,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1714,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken":{ + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc":{ + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2182,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -3221,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3231,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3241,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3291,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 06cf083567a..c7465f915e8 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Centre." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favourite" }, + "unfavorite": { + "message": "Unfavourite" + }, + "itemAddedToFavorites": { + "message": "Item added to favourites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favourites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favourites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "OK" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organisation vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organisation SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organisation is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organisations cannot be accessed. Contact your organisation owner for assistance." } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index be2b1b0055e..0e3efdd15f1 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -176,10 +176,16 @@ "message": "Explore more features of your Bitwarden account on the web app." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Continue to Help Centre?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Learn more about how to use Bitwarden on the Help Centre." + }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." @@ -383,6 +389,15 @@ "favorite": { "message": "Favourite" }, + "unfavorite": { + "message": "Unfavourite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarised Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Shared" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business allows you to share your vault items with others by using an organisation. Learn more on the bitwarden.com website." }, "moveToOrganization": { "message": "Move to organisation" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favourites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Restored item" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Auto-filled item and saved URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "OK" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organisation vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organisation SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organisation is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organisations cannot be accessed. Contact your organisation owner for assistance." } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index fdbbe165465..af10b5719aa 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Obtén más sobre cómo utilizar Bitwarden en el Centro de Ayuda." }, + "continueToBrowserExtensionStore": { + "message": "¿Continuar a la tienda de extensiones del navegador?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Ayuda a otros a averiguar si Bitwarden es correcto para ellos. Visita la tienda de extensiones de tu navegador y deja una calificación ahora." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Puedes cambiar la contraseña maestra en la aplicación web de Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorito" }, + "unfavorite": { + "message": "Eliminar favorito" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notas" }, @@ -404,6 +419,9 @@ "launch": { "message": "Iniciar" }, + "launchWebsite": { + "message": "Iniciar página web" + }, "website": { "message": "Web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Sesión terminada" }, + "loggedOutDesc": { + "message": "Has cerrado sesión de tu cuenta." + }, "loginExpired": { "message": "Tu sesión ha expirado." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Desbloquear" }, + "additionalOptions": { + "message": "Opciones adicionales" + }, "enableContextMenuItem": { "message": "Mostrar las opciones de menú contextuales" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportar desde" + }, "exportVault": { "message": "Exportar caja fuerte" }, "fileFormat": { "message": "Formato de archivo" }, + "fileEncryptedExportWarningDesc": { + "message": "Esta exportación de archivo estará protegida por contraseña y requerirá la contraseña del archivo para descifrarla." + }, + "filePassword": { + "message": "Contraseña del archivo" + }, + "exportPasswordDescription": { + "message": "Esta contraseña se utilizará para exportar e importar este archivo" + }, + "accountRestrictedOptionDescription": { + "message": "Utiliza la clave de cifrado de tu cuenta, derivada del nombre de usuario y la contraseña maestra de tu cuenta, para cifrar la exportación y restringir la importación solo a la cuenta actual de Bitwarden." + }, + "passwordProtectedOptionDescription": { + "message": "Establece una contraseña de archivo para cifrar la exportación e importarlo a cualquier cuenta de Bitwarden utilizando la contraseña para el descifrado." + }, + "exportTypeHeading": { + "message": "Tipo de exportación" + }, + "accountRestricted": { + "message": "Cuenta restringida" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Contraseña de archivo\" y \"Confirmar contraseña de archivo\" no coinciden." + }, "warning": { "message": "ADVERTENCIA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifica la URL base de tu instalación de Bitwarden de alojamiento propio." }, + "selfHostedBaseUrlHint": { + "message": "Especifica la dirección URL base de la instalación de Bitwarden alojada localmente. Ejemplo: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Para una configuración avanzada, puedes especificar la dirección URL base de cada servicio de forma independiente." + }, + "selfHostedEnvFormInvalid": { + "message": "Debes añadir la dirección URL del servidor base o al menos un entorno personalizado." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Colecciones" }, + "nCollections": { + "message": "$COUNT$ colecciones", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritos" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Elemento restaurado" }, + "alreadyHaveAccount": { + "message": "¿Ya tienes una cuenta?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Cerrar sesión eliminará todo el acceso a tu caja fuerte y requerirá autenticación en línea después del tiempo de espera. ¿Estás seguro de que quieres usar esta configuración?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Autorellenar y guardar" }, + "fillAndSave": { + "message": "Rellenar y guardar" + }, "autoFillSuccessAndSavedUri": { "message": "Objeto autorellenado y URI guardada" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Error de actualización del token de acceso" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No se encontró ningún token de actualización ni clave de API. Intenta cerrar sesión y volver a iniciarla." + }, "desktopSyncVerificationTitle": { "message": "Verificación de sincronización del escritorio" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exportando caja fuerte de la organización" + }, + "exportingOrganizationVaultDesc": { + "message": "Solo se exportará la caja fuerte de la organización asociada a $ORGANIZATION$. Los elementos en las cajas fuertes individuales o de otras organizaciones no serán incluidos.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Se requiere un identificador único de inicio de sesión de la organización." }, + "creatingAccountOn": { + "message": "Crea una cuenta en" + }, + "checkYourEmail": { + "message": "Comprueba tu correo electrónico" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Sigue el enlace en el correo electrónico enviado a" + }, + "andContinueCreatingYourAccount": { + "message": "y continúa creando tu cuenta." + }, + "noEmail": { + "message": "¿Sin correo electrónico?" + }, + "goBack": { + "message": "Volver" + }, + "toEditYourEmailAddress": { + "message": "para editar la dirección de correo electrónico." + }, "eu": { "message": "Unión Europea", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Limpia los filtros o prueba otro término de búsqueda" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copiar información, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "Más información, $ITEMNAME$", + "copyInfoTitle": { + "message": "Copiar información - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copiar nota, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copiar nota - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "Más opciones, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Más opciones - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Ver elemento - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Asignar colecciones" + }, + "copyEmail": { + "message": "Copiar correo electrónico" + }, + "copyPhone": { + "message": "Copiar teléfono" + }, + "copyAddress": { + "message": "Copiar dirección" + }, "adminConsole": { "message": "Consola de administrador" }, @@ -3230,7 +3406,7 @@ "message": "Error al asignar la carpeta de destino." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Ver elementos en $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Volver a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Nuevo" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Eliminar $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Elementos sin carpeta" + }, + "organizationIsDeactivated": { + "message": "La organización está desactivada" + }, + "contactYourOrgAdmin": { + "message": "No se puede acceder a los elementos de las organizaciones desactivadas. Ponte en contacto con el propietario de tu organización para obtener ayuda." } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 0d38859fead..6d1a2f7635e 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Lemmik" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Märkmed" }, @@ -404,6 +419,9 @@ "launch": { "message": "Käivita" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Veebileht" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Välja logitud" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sessioon on aegunud." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Lukusta lahti" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Kuva parema kliki menüü valikud" }, @@ -793,12 +817,39 @@ "message": "Solarized tume", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Ekspordi hoidla" }, "fileFormat": { "message": "Failivorming" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "HOIATUS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Kohandatud keskkond" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kogumikud" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Lemmikud" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Kirje on taastatud" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Väljalogimine eemaldab hoidlale ligipääsu ning nõuab pärast ajalõpu perioodi uuesti autentimist. Oled kindel, et soovid seda valikut kasutada?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Täida ja salvesta" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Kirje täideti ja URI salvestati" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Töölaua sünkroonimise kinnitamine" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Viga" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Nõutav on organisatsiooni SSO identifikaator." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EL", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 03703412b86..3ba8820abca 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Gogokoa" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Oharrak" }, @@ -404,6 +419,9 @@ "launch": { "message": "Abiarazi" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Webgunea" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Saioa itxita" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Saioa amaitu da." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Erakutsi laster-menuko aukerak" }, @@ -793,12 +817,39 @@ "message": "Solarized iluna", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Esportatu kutxa gotorra" }, "fileFormat": { "message": "Fitxategiaren formatua" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "KONTUZ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Bitwarden instalatzeko, zehaztu ostatatze propioaren oinarrizko URL-a." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Ingurune pertsonalizatua" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Bildumak" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Gogokoak" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Elementua berreskuratua" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Saioa ixteak kutxa gotorreko sarrera guztia kenduko du eta itxaronaldiaren ondoren lineako autentifikazioa eskatuko du. Ziur zaude hau egin nahi duzula?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-bete eta gorde" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Elementua auto-betea eta URIa gordeta" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ados" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Mahaigaineko sinkronizazioaren egiaztatzea" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Akatsa" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index ef8893b87c2..b5d383c1364 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "مورد علاقه" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "یادداشت‌ها" }, @@ -404,6 +419,9 @@ "launch": { "message": "راه اندازی" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "وب‌سایت" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "خارج شد" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "باز کردن قفل" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "نمایش گزینه‌های منوی زمینه" }, @@ -793,12 +817,39 @@ "message": "تاریک خورشیدی", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "برون ریزی گاوصندوق" }, "fileFormat": { "message": "فرمت پرونده" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "اخطار", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "محیط سفارشی" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "مجموعه‌ها" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "مورد علاقه‌ها" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "مورد بازیابی شد" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "خروج از سیستم، تمام دسترسی ها به گاو‌صندوق شما را از بین می‌برد و نیاز به احراز هویت آنلاین پس از مدت زمان توقف دارد. آیا مطمئن هستید که می‌خواهید از این تنظیمات استفاده کنید؟" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "پر کردن خودکار و ذخیره" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "مورد خودکار پر شد و نشانی اینترنتی ذخیره شد" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "تأیید" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "تأیید همگام‌سازی دسکتاپ" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "خطا" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "شناسه سازمان SSO مورد نیاز است." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "اروپا", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index c1647843c26..5cb434f1265 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -173,13 +173,19 @@ "message": "Avataanko verkkosovellus?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Tutustu Bitwarden-tilisi muihin ominaisuuksiin verkkosovelluksessa." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Avataanko tukikeskus?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Lue Tukikeskuksesta lisää Bitwardenin käytöstä." + }, + "continueToBrowserExtensionStore": { + "message": "Avataanko selaimen laajennuskauppa?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Auta muita selvittämään sopiiko Bitwarden heille. Arvioi se selaimesi laajennuskaupassa nyt." }, "changeMasterPasswordOnWebConfirmation": { "message": "Voit vaihtaa pääsalasanasi Bitwardenin verkkosovelluksessa." @@ -199,16 +205,16 @@ "message": "Kirjaudu ulos" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Tietoja Bitwardenista" }, "about": { "message": "Tietoja" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Lisää Bitwardenilta" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Avataanko bitwarden.com?" }, "bitwardenForBusiness": { "message": "Bitwarden for Business" @@ -217,25 +223,25 @@ "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 Authenticatorin avulla voit säilyttää todennusavaimet ja luoda TOTP-koodeja kaksivaiheista tunnistautumista varten. Lue lisää bitwarden.com-sivustolta." }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden Salaisuushallinta" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Säilytä, hallitse ja jaa kehittäjäsalaisuuksia turvallisesti Bitwardenin Salaisuushallinnalla. Lue lisää bitwarden.com-sivustolta." }, "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": "Luo perinteisistä salasanoista vapaita sujuvia ja turvallisia kirjautumisratkaisuja Passwordless.dev-palvelussa. Tutustu palveluun bitwarden.com-sivustolla." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Ilmainen Bitwarden Families" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Olet oikeutettu ilmaiseen Bitwarden Families -tilaukseen. Lunasta tarjous verkkosovelluksessa tänään." }, "version": { "message": "Versio" @@ -296,7 +302,7 @@ "message": "Luo kirjautumistiedoillesi automaattisesti vahvoja, ainutlaatuisia salasanoja." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden Verkkosovellus" }, "importItems": { "message": "Tuo kohteita" @@ -383,6 +389,15 @@ "favorite": { "message": "Suosikki" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Merkinnät" }, @@ -404,6 +419,9 @@ "launch": { "message": "Avaa" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Verkkosivusto" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Kirjauduttu ulos" }, + "loggedOutDesc": { + "message": "Sinut on kirjattu ulos tililtäsi." + }, "loginExpired": { "message": "Kirjautumisistuntosi on erääntynyt." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Avaa" }, + "additionalOptions": { + "message": "Lisäasetukset" + }, "enableContextMenuItem": { "message": "Näytä sisältövalikon valinnat" }, @@ -793,12 +817,39 @@ "message": "Solarized, tumma", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Vie lähteestä" + }, "exportVault": { "message": "Vie holvi" }, "fileFormat": { "message": "Tiedostomuoto" }, + "fileEncryptedExportWarningDesc": { + "message": "Tämä vientitiedosto suojataan salasanalla, joka on syötettävä ja salauksen purkamiseksi." + }, + "filePassword": { + "message": "Tiedoston salasana" + }, + "exportPasswordDescription": { + "message": "Tätä salasanaa käytetään tämän tiedoston viennissä ja tuonnissa" + }, + "accountRestrictedOptionDescription": { + "message": "Salaa vienti ja rajoita tuonti vain nykyiselle Bitwarden-tilille tilisi käyttäjätunnukseen ja pääsalasanaan pohjautuvalla salausavaimella." + }, + "passwordProtectedOptionDescription": { + "message": "Salaa vientitiedosto salasanalla, joka mahdollistaa sen tuonnin mille tahansa Bitwarden-tilille." + }, + "exportTypeHeading": { + "message": "Viennin tyyppi" + }, + "accountRestricted": { + "message": "Rajoitettu tilille" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Tiedoston salasana\" ja \"Vahvista tiedoston salasana\" eivät täsmää." + }, "warning": { "message": "VAROITUS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Jaettu" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business mahdollistaa holvisi sisällön jakamisen organisaation avulla. Lue lisää osoitteesta bitwarden.com." }, "moveToOrganization": { "message": "Siirrä organisaatiolle" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen pääverkkotunnus." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kokoelmat" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Suosikit" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Kohde palautettiin" }, + "alreadyHaveAccount": { + "message": "Onko sinulla jo tili?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Uloskirjautuminen estää pääsyn holviisi ja vaatii ajan umpeuduttua todennuksen Internet-yhteyden välityksellä. Haluatko varmasti käyttää asetusta?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automaattitäytä ja tallenna" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Kohde täytettiin automaattisesti ja URI tallennettiin" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Työpöytäsynkronoinnin vahvistus" }, @@ -1754,10 +1835,10 @@ "message": "Selaimen biometriaa ei tueta tällä laitteella." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Käyttäjä on lukittu tai kirjattu ulos" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Poista käyttäjän lukitus työpöytäsovelluksesta ja yritä uudelleen." }, "biometricsFailedTitle": { "message": "Biometria epäonnistui" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Organisaation holvin vienti" + }, + "exportingOrganizationVaultDesc": { + "message": "Vain organisaatioon $ORGANIZATION$ liitetyn holvin kohteet viedään. Yksityisen holvin ja muiden organisaatioiden kohteita ei sisällytetä.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Virhe" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organisaation kertakirjautumistunniste tarvitaan." }, + "creatingAccountOn": { + "message": "Luodaan tili palvelimelle" + }, + "checkYourEmail": { + "message": "Tarkasta sähköpostisi" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Seuraa viestin linkkiä, joka lähetettiin sähköpostitse osoitteeseen" + }, + "andContinueCreatingYourAccount": { + "message": "ja jatka tilin luontia." + }, + "noEmail": { + "message": "Etkö saanut viestiä?" + }, + "goBack": { + "message": "Palaa takaisin" + }, + "toEditYourEmailAddress": { + "message": "muokataksesi sähköpostiosoitettasi." + }, "eu": { "message": "EU", "description": "European Union" @@ -3177,21 +3291,21 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Automaattitäytä ehdotukset" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to auto-fill" + "message": "Tallenna sivustolle kirjautumiskohde automaattista täyttöä varten" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Holvisi on tyhjä" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Mikään kohde ei vastaa hakuasi" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Tyhjennä suodattimet tai kokeile toista hakutermiä" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Hallintapaneelista" }, @@ -3230,7 +3406,7 @@ "message": "Virhe määritettäessä kohdekansiota." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Näytä kohteen $NAME$ kohteet", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Palaa kohteeseen $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3250,10 +3426,10 @@ } }, "new": { - "message": "New" + "message": "Uusi" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Poista $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Kohteet, joilla ei ole kansioita" + }, + "organizationIsDeactivated": { + "message": "Organisaatio on poistettu käytöstä" + }, + "contactYourOrgAdmin": { + "message": "Käytöstä poistettujen organisaatioiden kohteet eivät ole käytettävissä. Ole yhteydessä organisaation omistajaan saadaksesi apua." } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index c75700da1c5..468e2a503f6 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Ang Paborito" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Mga nota" }, @@ -404,6 +419,9 @@ "launch": { "message": "Paglulunsad" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Umalis na" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Nag-expire na ang iyong session sa login." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "I-unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Ipakita ang mga opsyon ng menu ng konteksto" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "I-export vault" }, "fileFormat": { "message": "Format ng file" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "BABALA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Tukuyin ang base URL ng iyong Bitwarden installation na naka-host sa on-premises." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Kapaligirang Custom" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Koleksyon" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Mga Paborito" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item na nai-restore" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Sigurado ka bang gusto mong gamitin ang setting na ito? Pagsasara ay magtatanggal ng lahat ng access sa iyong vault at nangangailangan ng online authentication pagkatapos ng timeout period?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill at i-save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item na auto-filled at URI na nai-save" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Pag verify ng pag sync ng desktop" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Mali" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 4ace8752b3c..8bb8cfe537b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -173,13 +173,19 @@ "message": "Poursuivre vers l'application web ?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Explorez plus de fonctionnalités de votre compte Bitwarden sur l'application Web." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Continuer vers le centre d'aide ?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "En savoir plus sur l'utilisation de Bitwarden dans le centre d'aide." + }, + "continueToBrowserExtensionStore": { + "message": "Continuer vers le magasin d'extension du navigateur ?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Aidez les autres à savoir si Bitwarden est fait pour eux. Visitez le magasin d'extension de votre navigateur et laissez une évaluation maintenant." }, "changeMasterPasswordOnWebConfirmation": { "message": "Vous pouvez modifier votre mot de passe principal sur l'application web de Bitwarden." @@ -199,43 +205,43 @@ "message": "Se déconnecter" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "À propos de Bitwarden" }, "about": { "message": "À propos" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Plus de Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Continuer vers bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden pour les entreprises" }, "bitwardenAuthenticator": { "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 vous permet de stocker les clés d'authentification et de générer des codes TOTP pour les flux de vérification en 2 étapes. En savoir plus sur le site web bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Stockez, gérez et partagez des mots de passe de développement avec Bitwarden Secrets Manager. Apprenez-en plus sur le site 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": "Créez des expériences de connexion faciles et sécurisées à partir des mots de passe traditionnels avec Passwordless.dev. Apprenez-en plus sur le site web bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Bitwarden Familles gratuit" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Vous êtes éligible pour obtenir Bitwarden Familles gratuitement. Souscrivez à cette offre aujourd'hui dans l'application Web." }, "version": { "message": "Version" @@ -296,7 +302,7 @@ "message": "Générer automatiquement des mots de passe robustes et uniques pour vos identifiants." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Application web Bitwarden" }, "importItems": { "message": "Importer des éléments" @@ -383,6 +389,15 @@ "favorite": { "message": "Favori" }, + "unfavorite": { + "message": "Retirer des favoris" + }, + "itemAddedToFavorites": { + "message": "Élément ajouté aux favoris" + }, + "itemRemovedFromFavorites": { + "message": "Élément retiré des favoris" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Ouvrir" }, + "launchWebsite": { + "message": "Ouvrir le site web" + }, "website": { "message": "Site web" }, @@ -417,7 +435,7 @@ "message": "Autre" }, "unlockMethods": { - "message": "Unlock options" + "message": "Options de déverrouillage" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre." @@ -426,10 +444,10 @@ "message": "Configurer une méthode de déverrouillage dans les Paramètres" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Expiration de la session" }, "otherOptions": { - "message": "Other options" + "message": "Autres options" }, "rateExtension": { "message": "Noter l'extension" @@ -593,6 +611,9 @@ "loggedOut": { "message": "Déconnecté" }, + "loggedOutDesc": { + "message": "Vous avez été déconnecté de votre compte." + }, "loginExpired": { "message": "Votre session a expiré." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Déverrouiller" }, + "additionalOptions": { + "message": "Options supplémentaires" + }, "enableContextMenuItem": { "message": "Afficher les options du menu contextuel" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exporter à partir de" + }, "exportVault": { "message": "Exporter le coffre" }, "fileFormat": { "message": "Format de fichier" }, + "fileEncryptedExportWarningDesc": { + "message": "L'export de ce fichier sera protégé par un mot de passe et nécessitera le mot de passe du fichier pour être déchiffré." + }, + "filePassword": { + "message": "Mot de passe du fichier" + }, + "exportPasswordDescription": { + "message": "Ce mot de passe sera utilisé pour exporter et importer ce fichier" + }, + "accountRestrictedOptionDescription": { + "message": "Utilisez la clé de chiffrement de votre compte, dérivée du nom d'utilisateur et du mot de passe principal de votre compte, pour chiffrer l'export et restreindre l'import au seul compte Bitwarden actuel." + }, + "passwordProtectedOptionDescription": { + "message": "Définissez un mot de passe de fichier pour chiffrer l'exportation et l'importer dans n'importe quel compte Bitwarden en utilisant le mot de passe pour le déchiffrage." + }, + "exportTypeHeading": { + "message": "Type d'exportation" + }, + "accountRestricted": { + "message": "Compte restreint" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Le \"Mot de passe du fichier\" et le \"Confirmation du mot de passe du fichier\" ne correspondent pas." + }, "warning": { "message": "AVERTISSEMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Partagé" }, "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 pour entreprises vous permet de partager les éléments de votre coffre avec les autres en utilisant une organisation. Apprenez-en plus sur le site bitwarden.com." }, "moveToOrganization": { "message": "Déplacer vers l'organisation" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Spécifiez l'URL de base de votre installation Bitwarden auto-hébergée." }, + "selfHostedBaseUrlHint": { + "message": "Spécifiez l'URL de base de votre installation autohébergée par Bitwarden. Exemple : https://bitwarden.compagnie.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Pour une configuration avancée. Vous pouvez spécifier l'URL de base indépendamment pour chaque service." + }, + "selfHostedEnvFormInvalid": { + "message": "Vous devez ajouter soit l'URL du serveur de base, soit au moins un environnement personnalisé." + }, "customEnvironment": { "message": "Environnement personnalisé" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoris" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Élément restauré" }, + "alreadyHaveAccount": { + "message": "Vous avez déjà un compte ?" + }, "vaultTimeoutLogOutConfirmation": { "message": "La déconnexion supprimera tout accès à votre coffre et nécessitera une authentification en ligne après la période d'expiration. Êtes-vous sûr de vouloir utiliser ce paramètre ?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Saisir automatiquement et sauvegarder" }, + "fillAndSave": { + "message": "Remplir et enregistrer" + }, "autoFillSuccessAndSavedUri": { "message": "Élément saisi automatiquement et URI sauvegardé" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Erreur d'Actualisation du Jeton d'Accès" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Aucun jeton de rafraîchissement ou clé d'API trouvé. Veuillez essayer de vous déconnecter et de vous reconnecter." + }, "desktopSyncVerificationTitle": { "message": "Vérification de la synchronisation avec l'application de bureau" }, @@ -1754,10 +1835,10 @@ "message": "Le déverrouillage biométrique dans le navigateur n’est pas pris en charge sur cet appareil" }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Utilisateur verrouillé ou déconnecté" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Veuillez déverrouiller cet utilisateur dans l'application de bureau et réessayer." }, "biometricsFailedTitle": { "message": "Le déverrouillage biométique a échoué\n" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Export du coffre de l'organisation" + }, + "exportingOrganizationVaultDesc": { + "message": "Seul le coffre d'organisation associé à $ORGANIZATION$ sera exporté. Les éléments dans les coffres individuels ou d'autres organisations ne seront pas inclus.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Erreur" }, @@ -2223,7 +2316,7 @@ "message": "Générer un alias de courriel avec un service de transfert externe." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "Erreur $SERVICENAME$ : $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2237,11 +2330,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Généré par Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Site web : $WEBSITE$. Généré par Bitwarden.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2251,7 +2344,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Jeton API $SERVICENAME$ invalide", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2261,7 +2354,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Jeton API $SERVICENAME$ non valide : $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2275,7 +2368,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Impossible d'obtenir l'ID du compte de courriel masqué $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2285,7 +2378,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Domaine de $SERVICENAME$ invalide.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2295,7 +2388,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "L'URL $SERVICENAME$ invalide.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2305,7 +2398,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Une erreur $SERVICENAME$ inconnue s'est produite.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2315,7 +2408,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Émetteur inconnu: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Identifiant SSO de l'organisation requis." }, + "creatingAccountOn": { + "message": "Création du compte sur" + }, + "checkYourEmail": { + "message": "Vérifiez vos courriels" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Suivez le lien dans le courriel envoyé à" + }, + "andContinueCreatingYourAccount": { + "message": "et continuer à créer votre compte." + }, + "noEmail": { + "message": "Pas de courriel ?" + }, + "goBack": { + "message": "Revenir en arrière" + }, + "toEditYourEmailAddress": { + "message": "pour modifier votre adresse mail." + }, "eu": { "message": "UE", "description": "European Union" @@ -3163,7 +3277,7 @@ "message": "Clé d'identification (passkey) retirée" }, "unassignedItemsBannerNotice": { - "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." + "message": "Remarque : Les éléments d'organisation non assignés ne sont plus visibles dans la vue Tous les coffres et ne sont maintenant accessibles que via la Console Admin." }, "unassignedItemsBannerSelfHostNotice": { "message": "Remarque : À partir du 16 mai 2024, les éléments d'organisation non assignés ne seront plus visibles dans votre vue Tous les coffres sur les appareils et ne seront maintenant accessibles que via la Console Admin." @@ -3177,22 +3291,22 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Suggestions de saisie automatique" }, "autofillSuggestionsTip": { "message": "Save a login item for this site to auto-fill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Votre coffre-fort est vide" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Aucun élément ne correspond à votre recherche" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Effacer les filtres ou essayer un autre terme de recherche" }, - "copyInfo": { - "message": "Copy info, $ITEMNAME$", + "copyInfoLabel": { + "message": "Copier les informations, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "More options, $ITEMNAME$", + "copyInfoTitle": { + "message": "Copier les informations - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copier la note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copier la note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "Plus d'options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,26 +3355,58 @@ } } }, + "moreOptionsTitle": { + "message": "Plus d'options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Voir l'élément - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assigner une collection" + }, + "copyEmail": { + "message": "Copier l'email" + }, + "copyPhone": { + "message": "Copier le numéro de téléphone" + }, + "copyAddress": { + "message": "Copier l'adresse" + }, "adminConsole": { "message": "Console Admin" }, "accountSecurity": { - "message": "Account security" + "message": "Sécurité du compte" }, "notifications": { "message": "Notifications" }, "appearance": { - "message": "Appearance" + "message": "Apparence" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Erreur lors de l'assignation de la collection cible." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Erreur lors de l'assignation du dossier cible." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Voir les éléments dans $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Retour à $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3250,10 +3426,10 @@ } }, "new": { - "message": "New" + "message": "Nouveau" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Retirer $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Eléments sans dossier" + }, + "organizationIsDeactivated": { + "message": "L'organisation est désactivée" + }, + "contactYourOrgAdmin": { + "message": "Les éléments des Organisations désactivées ne sont pas accessibles. Contactez le propriétaire de votre Organisation pour obtenir de l'aide." } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 873e8196888..cf38b90e8fa 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Podes cambiar o teu contrasinal mestre na aplicación web de Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorito" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notas" }, @@ -404,6 +419,9 @@ "launch": { "message": "Iniciar" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Sitio web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Sesión pechada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "A túa sesión caducou." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Desbloquear" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarizado escuro", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exportar caixa forte" }, "fileFormat": { "message": "Formato de ficheiro" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ADVERTENCIA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Coleccións" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritos" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Erro" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "UE", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 3a31c9f4edf..b5cbb79ca17 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "מועדף" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "הערות" }, @@ -404,6 +419,9 @@ "launch": { "message": "הפעל" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "אתר" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "בוצעה יציאה" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "יצוא כספת" }, "fileFormat": { "message": "פורמט קובץ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "אזהרה", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "הזן את כתובת השרת המקומי של Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "אוספים" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "מועדפים" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "פריט ששוחזר" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "יציאה מהחשבון תסיר את כל הגישה לכספת ויידרש אימות מקוון לאחר משך הזמן שהוקצב. האם אתה בטוח שברצונך להשתמש בהגדרה זו?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "בצע השלמה אוטומטית ושמור" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "בוצעה השלמה אוטומטית והכתובת נשמרה" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "אישור" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "אימות סנכרון מול שולחן העבודה" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "שגיאה" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index f70d97e3406..741c873c65c 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "नोट्स" }, @@ -404,6 +419,9 @@ "launch": { "message": "खोलें" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "वेबसाइट" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "लॉग आउट" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "अपने लॉगिन सत्र समाप्त हो गया है।" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "संदर्भ मेनू विकल्प दिखाएं" }, @@ -793,12 +817,39 @@ "message": "सौरीकृत अंधेरा", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export Vault" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "चेतावनी", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "संग्रह" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "बहाल आइटम" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "लॉग आउट करने से वॉल्टमें प्रवेश संभव नहीं होगा और समय समाप्त होने के बाद ऑनलाइन प्रमाणीकरण की आश्यकता होगी। आप इस सेटिंग्स को प्रयोग करने के लिए विश्वस्त हैं?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "ऑटो फिल और सेव" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "ऑटो फिल आइटम और सेव URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "ठीक है" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "डेस्कटॉप सिंक सत्यापन" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "एरर" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 9a4419c77ba..62b987fc3aa 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorit" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Bilješke" }, @@ -404,6 +419,9 @@ "launch": { "message": "Pokreni" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Web stranica" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesija je istekla." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Otključaj" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Prikaži opcije kotekstualnog izbornika" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvezi trezor" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "UPOZORENJE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Navedi osnovni URL svoje lokalno smještene Bitwarden instalacije." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Zbirke" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoriti" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Stavka vraćena" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Odjava će ukloniti pristup tvom trezoru i zahtijevati mrežnu potvrdu identiteta nakon isteka vremenske neaktivnosti. Sigurno želiš koristiti ovu postavku?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-ispuni i spremi" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Auto-ispunjena stavka i spremanje URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "U redu" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Potvrda desktop sinkronizacije" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Pogreška" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Potreban je identifikator organizacije." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index d5276f341e6..432ab7ec26e 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "További információ a Bitwarden használatáról a Segítség Központban." }, + "continueToBrowserExtensionStore": { + "message": "Továbblépés a böngésző bővítmények áruházba?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Segítsünk másoknak megtudni, hogy a Bitwarden megfelelő-e számukra. Látogassunk el a böngésző bővítmény áruházba és értékeljük most." + }, "changeMasterPasswordOnWebConfirmation": { "message": "A mesterjelszó a Bitwarden webalkalmazásban módosítható." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Kedvenc" }, + "unfavorite": { + "message": "Nem kedvenc" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Jegyzetek" }, @@ -404,6 +419,9 @@ "launch": { "message": "Indítás" }, + "launchWebsite": { + "message": "Webhely indítása" + }, "website": { "message": "Weboldal" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Kijelentkezett" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Bejelentkezési munkamenete lejárt." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Feloldás" }, + "additionalOptions": { + "message": "Kiegészítő opciók" + }, "enableContextMenuItem": { "message": "Helyi menü opciók megjelenítése" }, @@ -793,12 +817,39 @@ "message": "Szolarizált sötét", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportálás innen:" + }, "exportVault": { "message": "Széf exportálása" }, "fileFormat": { "message": "Fájlformátum" }, + "fileEncryptedExportWarningDesc": { + "message": "Ez a fájl exportálás jelszóval védett és a visszafejtéshez a fájl jelszó megadása szükséges." + }, + "filePassword": { + "message": "Fájl jelszó" + }, + "exportPasswordDescription": { + "message": "Ezt a jelszó kerül használatba a fájl exportálására és importálására." + }, + "accountRestrictedOptionDescription": { + "message": "Használjuk a fiók felhasználónevéből és mesterjelszavából származó fióktitkosítási kulcsot az exportálás titkosításához és az importálást csak az aktuális Bitwarden fiókra korlátozzuk." + }, + "passwordProtectedOptionDescription": { + "message": "Állítsunk be egy fájl jelszót az exportálás titkosításához és importáljuk azt bármely Bitwarden fiókba a visszafejtéshez használt jelszó használatával." + }, + "exportTypeHeading": { + "message": "Exportálási típus" + }, + "accountRestricted": { + "message": "Korlátozott fiók" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "A “Fájl jelszó” és a “Fájl jelszó megerősítés“ nem egyezik." + }, "warning": { "message": "FIGYELEM", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "A helyileg működtetett Bitwarden telepítés alap webcímének megadása." }, + "selfHostedBaseUrlHint": { + "message": "Adjuk meg a helyileg tárolt Bitwarden telepítés alap webcímét. Példa: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Speciális konfigurációhoz külön-külön megadhatjuk az egyes szolgáltatások alap webcímét." + }, + "selfHostedEnvFormInvalid": { + "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." + }, "customEnvironment": { "message": "Egyedi környezet" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Gyűjtemények" }, + "nCollections": { + "message": "$COUNT$ gyűjtemény", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Kedvencek" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Visszaállított elem" }, + "alreadyHaveAccount": { + "message": "Van már saját fiók?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Kijelentkezve az összes széf elérés eltávolításra kerül és webes hitelesítésre van szükség az időkifutás után. Biztosan szeretnénk használni ezt a beállítást?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automatikus kitöltés és mentés" }, + "fillAndSave": { + "message": "Kitöltés és mentés" + }, "autoFillSuccessAndSavedUri": { "message": "Automatikusan kitöltött elem és mentett URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Asztali szinkronizálás ellenőrzés" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Szervezeti széf exportálása" + }, + "exportingOrganizationVaultDesc": { + "message": "Csak $ORGANIZATION$ névvel társított szervezeti széf kerül exportálásra. Ebbe nem kerülnek be a személyes és más szervezeti széf elemek.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Hiba" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "A szervezeti SSO azonosító megadása szükséges." }, + "creatingAccountOn": { + "message": "Fiók létrehozása:" + }, + "checkYourEmail": { + "message": "Email cím postaláda ellenőrzése" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kövessük a hivatkozást az elküldött emailben" + }, + "andContinueCreatingYourAccount": { + "message": "és folytassuk a fiók létrehozását." + }, + "noEmail": { + "message": "Nem érkezett email?" + }, + "goBack": { + "message": "Vissza" + }, + "toEditYourEmailAddress": { + "message": "az email cím szerkesztéséhez." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "Töröljük a szűrőket vagy próbálkozzunk másik keresési kifejezéssel." }, - "copyInfo": { - "message": "Adatok másolása, $ITEMNAME$", + "copyInfoLabel": { + "message": "Infó másolása, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Infó másolása - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Jegyzet másolása, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Jegyzet másolása - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "További opciók, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "További opciók - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Elem megtekkintése - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Gyűjtemények hozzárendelése" + }, + "copyEmail": { + "message": "Email cím másolása" + }, + "copyPhone": { + "message": "Telefonszám másolása" + }, + "copyAddress": { + "message": "Cím másolása" + }, "adminConsole": { "message": "Adminisztrátori konzol" }, @@ -3230,7 +3406,7 @@ "message": "Hiba történt a célmappa hozzárendelése során." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ elemek megtekintése", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Vissza: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Új" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ eltávolítása", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 57915c8f4a3..3f834efd68a 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -173,13 +173,19 @@ "message": "Lanjut ke aplikasi web?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Temukan fitur Bitwarden lebih melalui aplikasi web." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Lanjutkan ke Pusat Bantuan?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Pelajari lebih lanjut penggunaan Bitwarden di Pusat Bantuan." + }, + "continueToBrowserExtensionStore": { + "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." }, "changeMasterPasswordOnWebConfirmation": { "message": "Anda dapat merubah sandi utama di aplikasi Bitwarden web." @@ -383,6 +389,15 @@ "favorite": { "message": "Favorit" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Catatan" }, @@ -404,6 +419,9 @@ "launch": { "message": "Luncurkan" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Situs Web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Keluar" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Gelap Solarized", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Ekspor Brankas" }, "fileFormat": { "message": "Format Berkas" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "PERINGATAN", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "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" + }, + "selfHostedCustomEnvHeader": { + "message": "For advanced configuration, you can specify the base URL of each service independently." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Lingkungan Khusus" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Koleksi" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorit" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item Yang Dipulihkan" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Isi Otomatis dan Simpan" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item yang Diisi Otomatis dan URI Tersimpan" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Oke" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verifikasi sinkronisasi desktop" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Galat" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index b5f9a8760be..72bc28c87eb 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Scopri di più su come usare Bitwarden nel centro assistenza." }, + "continueToBrowserExtensionStore": { + "message": "Continua sullo store delle estensioni per il browser?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Aiuta gli altri a scoprire se Bitwarden è adatto a loro. Visita lo store delle estensioni del tuo browser e lascia una recensione." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Puoi modificare la tua password principale sul sito web di Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Preferito" }, + "unfavorite": { + "message": "Rimuovi dai preferiti" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Note" }, @@ -404,6 +419,9 @@ "launch": { "message": "Avvia" }, + "launchWebsite": { + "message": "Avvia il sito web" + }, "website": { "message": "Sito web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Disconnesso" }, + "loggedOutDesc": { + "message": "Sei stato fatto uscire dal tuo account." + }, "loginExpired": { "message": "La tua sessione è scaduta." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Sblocca" }, + "additionalOptions": { + "message": "Opzioni aggiuntive" + }, "enableContextMenuItem": { "message": "Mostra opzioni nel menu contestuale" }, @@ -793,12 +817,39 @@ "message": "Scuro solarizzato", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Esporta da" + }, "exportVault": { "message": "Esporta cassaforte" }, "fileFormat": { "message": "Formato file" }, + "fileEncryptedExportWarningDesc": { + "message": "Questo file esportato sarà protetto e richiederà la password del file per decifrarlo." + }, + "filePassword": { + "message": "Password del file" + }, + "exportPasswordDescription": { + "message": "La password sarà utilizzata per importare ed esportare questo file" + }, + "accountRestrictedOptionDescription": { + "message": "Usa la chiave di crittografia dell'account, derivata dal nome utente e dalla password principale del tuo account, per crittografare il file di esportazione e limitare l'importazione solo all'account Bitwarden corrente." + }, + "passwordProtectedOptionDescription": { + "message": "Imposta una password del file per crittografare il file esportato e importarlo in qualsiasi account Bitwarden usando la password per decrittografarlo." + }, + "exportTypeHeading": { + "message": "Tipo di esportazione" + }, + "accountRestricted": { + "message": "Account limitato" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Le due password del file non corrispondono." + }, "warning": { "message": "ATTENZIONE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specifica l'URL principale della tua installazione self-hosted di Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Specifica lo URL principale della tua installazione self-hosted di Bitwarden. Esempio: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Per la configurazione avanzata, puoi specificare lo URL di base di ciascun servizio in modo indipendente." + }, + "selfHostedEnvFormInvalid": { + "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." + }, "customEnvironment": { "message": "Ambiente personalizzato" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Raccolte" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Preferiti" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Elemento ripristinato" }, + "alreadyHaveAccount": { + "message": "Hai già un account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Uscire rimuoverà tutti gli accessi alla tua cassaforte e richiede l'autenticazione online dopo il periodo di timeout. Sei sicuro di voler usare questa opzione?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Riempi automaticamente e salva" }, + "fillAndSave": { + "message": "Riempi e salva" + }, "autoFillSuccessAndSavedUri": { "message": "Elemento riempito automaticamente e URI salvato" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Errore di aggiornamento del token di accesso" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nessun token di aggiornamento o chiave API trovati. Prova ad uscire ed entrare di nuovo." + }, "desktopSyncVerificationTitle": { "message": "Verifica sincronizzazione desktop" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Esportando cassaforte dell'organizzazione" + }, + "exportingOrganizationVaultDesc": { + "message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata. Elementi nelle casseforti individuali o in altre organizzazioni non saranno inclusi.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Errore" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Identificatore SSO dell'organizzazione obbligatorio." }, + "creatingAccountOn": { + "message": "Creazione account su" + }, + "checkYourEmail": { + "message": "Controlli la tua email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Segui il link nell'email inviata a" + }, + "andContinueCreatingYourAccount": { + "message": "e continua a creare il tuo account." + }, + "noEmail": { + "message": "Nessuna email?" + }, + "goBack": { + "message": "Torna indietro" + }, + "toEditYourEmailAddress": { + "message": "per modificare il tuo indirizzo email." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Cancella i filtri o prova un altro termine di ricerca" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copia informazioni, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copia informazioni - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copia nota, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copia nota - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Più opzioni, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Più opzioni - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Visualizza elemento - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assegna raccolte" + }, + "copyEmail": { + "message": "Copia email" + }, + "copyPhone": { + "message": "Copia telefono" + }, + "copyAddress": { + "message": "Copia indirizzo" + }, "adminConsole": { "message": "Console di amministrazione" }, @@ -3230,7 +3406,7 @@ "message": "Errore nell'assegnazione della cartella di destinazione." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizza gli elementi in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Torna a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Nuovo" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Rimuovi $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Elementi senza cartella" + }, + "organizationIsDeactivated": { + "message": "L'organizzazione è disattivata" + }, + "contactYourOrgAdmin": { + "message": "Non puoi accedere agli elementi nelle organizzazioni disattivate. Contatta il proprietario della tua organizzazione per ricevere assistenza." } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 877a678f5f5..549b441e506 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Bitwarden のヘルプセンターで使用方法の詳細をご覧ください。" }, + "continueToBrowserExtensionStore": { + "message": "ブラウザの拡張機能ストアに進みますか?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Bitwarden を他のユーザーにもおすすめしましょう。ブラウザーの拡張機能ストアにアクセスして、レビューをしてください。" + }, "changeMasterPasswordOnWebConfirmation": { "message": "Bitwarden ウェブアプリでマスターパスワードを変更できます。" }, @@ -383,6 +389,15 @@ "favorite": { "message": "お気に入り" }, + "unfavorite": { + "message": "お気に入り解除" + }, + "itemAddedToFavorites": { + "message": "アイテムをお気に入りに追加しました" + }, + "itemRemovedFromFavorites": { + "message": "アイテムをお気に入りから削除しました" + }, "notes": { "message": "メモ" }, @@ -404,6 +419,9 @@ "launch": { "message": "開く" }, + "launchWebsite": { + "message": "ウェブサイトを開く" + }, "website": { "message": "ウェブサイト" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "ログアウトしました" }, + "loggedOutDesc": { + "message": "アカウントからログアウトしました。" + }, "loginExpired": { "message": "ログインセッションの有効期限が切れています。" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "ロック解除" }, + "additionalOptions": { + "message": "追加オプション" + }, "enableContextMenuItem": { "message": "コンテキストメニューオプションを表示" }, @@ -793,12 +817,39 @@ "message": "Solarized ダーク", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "エクスポート元" + }, "exportVault": { "message": "保管庫のエクスポート" }, "fileFormat": { "message": "ファイル形式" }, + "fileEncryptedExportWarningDesc": { + "message": "エクスポートするファイルはパスワードで保護され、復号するにはファイルパスワードが必要になります。" + }, + "filePassword": { + "message": "ファイルパスワード" + }, + "exportPasswordDescription": { + "message": "このパスワードはこのファイルのエクスポートとインポート時に使用します" + }, + "accountRestrictedOptionDescription": { + "message": "アカウントのユーザー名とマスターパスワードから得られる暗号化キーを使用してエクスポートするデータを暗号化し、現在の Bitwarden アカウントのみがインポートできるよう制限します。" + }, + "passwordProtectedOptionDescription": { + "message": "エクスポートを暗号化するためのファイルパスワードを設定します。そのパスワードを使用して、任意の Bitwarden アカウントにインポートします。" + }, + "exportTypeHeading": { + "message": "エクスポートの種類" + }, + "accountRestricted": { + "message": "アカウント制限" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "「ファイルパスワード」と「ファイルパスワードの確認」が一致しません。" + }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "セルフホスティングしている Bitwarden のベース URL を指定してください。" }, + "selfHostedBaseUrlHint": { + "message": "オンプレミスホストした Bitwarden のベース URL を指定してください。例: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "高度な設定では、各サービスのベース URL を個別に指定できます。" + }, + "selfHostedEnvFormInvalid": { + "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" + }, "customEnvironment": { "message": "カスタム環境" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "コレクション" }, + "nCollections": { + "message": "$COUNT$ 個のコレクション", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "お気に入り" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "リストアされたアイテム" }, + "alreadyHaveAccount": { + "message": "すでにアカウントをお持ちですか?" + }, "vaultTimeoutLogOutConfirmation": { "message": "ログアウトすると保管庫へのすべてのアクセスが制限され、タイムアウト期間後にオンライン認証が必要になります。 この設定を使用してもよろしいですか?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "自動入力して保存" }, + "fillAndSave": { + "message": "入力して保存" + }, "autoFillSuccessAndSavedUri": { "message": "アイテムを自動入力して URI を保存しました" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "OK" }, + "errorRefreshingAccessToken": { + "message": "アクセストークンの更新エラー" + }, + "errorRefreshingAccessTokenDesc": { + "message": "リフレッシュトークンや API キーが見つかりませんでした。ログアウトして再度ログインしてください。" + }, "desktopSyncVerificationTitle": { "message": "デスクトップ同期の検証" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "組織保管庫のエクスポート" + }, + "exportingOrganizationVaultDesc": { + "message": "$ORGANIZATION$ に関連付けられた組織保管庫のみがエクスポートされます。個々の保管庫または他の組織にあるアイテムは含まれません。", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "エラー" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "組織の SSO ID が必要です。" }, + "creatingAccountOn": { + "message": "アカウント作成:" + }, + "checkYourEmail": { + "message": "メールをご確認ください" + }, + "followTheLinkInTheEmailSentTo": { + "message": "アカウントの作成を続けるには" + }, + "andContinueCreatingYourAccount": { + "message": "に送られたメールのリンクを開いてください。" + }, + "noEmail": { + "message": "メールがありませんか?" + }, + "goBack": { + "message": "戻って" + }, + "toEditYourEmailAddress": { + "message": "メールアドレスを編集してください。" + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "フィルタをクリアするか、別の検索ワードをお試しください" }, - "copyInfo": { + "copyInfoLabel": { "message": "$ITEMNAME$ の情報をコピー", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "情報をコピー - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "$ITEMNAME$ のメモをコピー", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "メモをコピー - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "$ITEMNAME$ のその他のオプション", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "その他のオプション - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "アイテムを表示 - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "コレクションを割り当て" + }, + "copyEmail": { + "message": "メールアドレスをコピー" + }, + "copyPhone": { + "message": "電話番号をコピー" + }, + "copyAddress": { + "message": "住所をコピー" + }, "adminConsole": { "message": "管理コンソール" }, @@ -3230,7 +3406,7 @@ "message": "ターゲットフォルダーの割り当てに失敗しました。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ のアイテムを表示", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "$NAME$ に戻る", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "新規作成" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ を削除", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "フォルダーがないアイテム" + }, + "organizationIsDeactivated": { + "message": "組織は無効化されています" + }, + "contactYourOrgAdmin": { + "message": "無効化された組織のアイテムにアクセスすることはできません。組織の所有者に連絡してください。" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 27883b2a07a..1129d83b6cc 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "რჩეული" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "ვებგვერდი" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index f1d190ab5d8..e0e5d75cecd 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "ಮೆಚ್ಚಿನ" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "ಟಿಪ್ಪಣಿಗಳು" }, @@ -404,6 +419,9 @@ "launch": { "message": "ಶುರು" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "ಜಾಲತಾಣ" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "ಲಾಗ್ ಔಟ್" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "ನಿಮ್ಮ ಲಾಗಿನ್ ಸೆಷನ್ ಅವಧಿ ಮೀರಿದೆ." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "ಡಾರ್ಕ್ ಸೌರ", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "ರಫ್ತು ವಾಲ್ಟ್" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ಎಚ್ಚರಿಕೆ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "ನಿಮ್ಮ ಆನ್-ಪ್ರಮೇಯ ಹೋಸ್ಟ್ ಮಾಡಿದ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಸ್ಥಾಪನೆಯ ಮೂಲ URL ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಿ." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "ಸಂಗ್ರಹಣೆಗಳು" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "ಮೆಚ್ಚುಗೆಗಳು" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "ಐಟಂ ಅನ್ನು ಮರುಸ್ಥಾಪಿಸಲಾಗಿದೆ" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "ಲಾಗ್ out ಟ್ ಆಗುವುದರಿಂದ ನಿಮ್ಮ ವಾಲ್ಟ್‌ನ ಎಲ್ಲಾ ಪ್ರವೇಶವನ್ನು ತೆಗೆದುಹಾಕುತ್ತದೆ ಮತ್ತು ಕಾಲಾವಧಿ ಅವಧಿಯ ನಂತರ ಆನ್‌ಲೈನ್ ದೃ hentic ೀಕರಣದ ಅಗತ್ಯವಿದೆ. ಈ ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ಬಳಸಲು ನೀವು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "ಸ್ವಯಂ ಭರ್ತಿ ಮತ್ತು ಉಳಿಸಿ" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "ಸ್ವಯಂ ತುಂಬಿದ ಐಟಂ ಮತ್ತು ಉಳಿಸಿದ ಯುಆರ್ಐ" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "ಸರಿ" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "ಡೆಸ್ಕ್ಟಾಪ್ ಸಿಂಕ್ ಪರಿಶೀಲನೆ" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index c5aa76dca7c..58d8d01b907 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Bitwarden 웹 앱에서 마스터 비밀번호를 변경할 수 있습니다." }, @@ -383,6 +389,15 @@ "favorite": { "message": "즐겨찾기" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "메모" }, @@ -404,6 +419,9 @@ "launch": { "message": "열기" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "웹 사이트" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "로그아웃됨" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "로그인 세션이 만료되었습니다." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "잠금 해제" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "보관함 내보내기" }, "fileFormat": { "message": "파일 형식" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "경고", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "사용자 지정 환경" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "컬렉션" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "즐겨찾기" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "복원된 항목" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "로그아웃하면 보관함에 대한 모든 접근이 제거되며 시간 제한을 초과하면 온라인 인증을 요구합니다. 정말로 이 설정을 사용하시겠습니까?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "자동 완성 및 저장" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "항목을 자동 완성하고 URI를 저장함" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "확인" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "데스크톱과의 동기화 인증" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "오류" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 9dff31e617a..8dd871d7ef3 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Pagrindinį slaptažodį galite pakeisti „Bitwarden“ žiniatinklio programėlėje." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Mėgstamas" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Pastabos" }, @@ -404,6 +419,9 @@ "launch": { "message": "Paleisti" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Tinklapis" }, @@ -417,7 +435,7 @@ "message": "Kita" }, "unlockMethods": { - "message": "Unlock options" + "message": "Atrakinti parinktis" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Nustatyk atrakinimo būdą, kad pakeistum saugyklos laiko limito veiksmą." @@ -426,10 +444,10 @@ "message": "Nustatykite nustatymuose atrakinimo metodą" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Baigėsi seanso laikas" }, "otherOptions": { - "message": "Other options" + "message": "Kitos parinktys" }, "rateExtension": { "message": "Įvertinkite šį plėtinį" @@ -593,6 +611,9 @@ "loggedOut": { "message": "Atsijungta" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesijos laikas baigėsi." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Atrakinti" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Rodyti kontekstinio meniu pasririnkimus" }, @@ -793,12 +817,39 @@ "message": "Saulėtas tamsą", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Eksportuoti saugyklą" }, "fileFormat": { "message": "Failo formatas" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ĮSPĖJIMAS", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Nurodykite vietinio priglobto „Bitwarden“ diegimo bazinį URL." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Individualizuota aplinka" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kolekcijos" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Mėgstamiausi" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Elementas atkurtas" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Atsijungus bus pašalinta visa Jūsų prieiga prie saugyklos, o pasibaigus skirtajam laikotarpiui bus reikalinga autentifikacija internetu. Ar tikrai norite naudoti šį nustatymą?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automatiškai užpildyti ir išsaugoti" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Elementas automatiškai užpildytas ir URI išsaugotas" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Gerai" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Darbalaukio sinchronizavimo verifikavimas" }, @@ -1754,10 +1835,10 @@ "message": "Šiame įrenginyje biometrikos negalima naudoti." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Naudotojas užrakintas arba atsijungęs" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Atrakinkite šį naudotoją darbalaukio programoje ir bandykite dar kartą." }, "biometricsFailedTitle": { "message": "Biometrika nepavyko" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Klaida" }, @@ -2223,7 +2316,7 @@ "message": "Sugeneruoti el. pašto slapyvardį su išorine persiuntimo paslauga." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "„$SERVICENAME$“ klaida: $ERRORMESSAGE$.", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2237,11 +2330,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Sugeneravo „Bitwarden“.", "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Svetainė: $WEBSITE$. Sugeneravo „Bitwarden“.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2251,7 +2344,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Netinkamas „$SERVICENAME$“ API prieigos raktas.", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2261,7 +2354,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Netinkamas „$SERVICENAME$“ API prieigos raktas: $ERRORMESSAGE$.", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2275,7 +2368,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Nepavyksta gauti „$SERVICENAME$“ užmaskuoto el. pašto paskyros ID.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2285,7 +2378,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Netinkamas „$SERVICENAME$“ domenas.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2295,7 +2388,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Netinkamas „$SERVICENAME$“ URL.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2305,7 +2398,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Įvyko nežinoma „$SERVICENAME$“ klaida.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2315,7 +2408,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Nežinomas persiuntėjas: „$SERVICENAME$“.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organizacijos SSO identifikatorius yra reikalingas." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "ES", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,17 +3355,49 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Administratoriaus konsolės" }, "accountSecurity": { - "message": "Account security" + "message": "Paskyros saugumas" }, "notifications": { - "message": "Notifications" + "message": "Pranešimai" }, "appearance": { - "message": "Appearance" + "message": "Išvaizda" }, "errorAssigningTargetCollection": { "message": "Klaida priskiriant tikslinę kolekciją." @@ -3250,7 +3426,7 @@ } }, "new": { - "message": "New" + "message": "Naujas" }, "removeItem": { "message": "Remove $NAME$", @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 33c390f9e03..fcc4851d390 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Vairāk par to, kā izmantot Bitwarden, var uzzināt palīdzības centrā." }, + "continueToBrowserExtensionStore": { + "message": "Turpināt pārlūka paplašinājumu veikalā?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Var palīdzēt citiem noskaidrot, vai Bitwarden viņiem der. To var izdarīt pārlūka lietotņu veikalā, atstājot vērtējumu." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Savu galveno paroli var mainīt Bitwarden tīmekļa lietotnē." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Izlasē" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Piezīmes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Palaist" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Tīmekļa vietne" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Atteicies" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Pieteikšanās sesija ir beigusies." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Atslēgt" }, + "additionalOptions": { + "message": "Papildu iespējas" + }, "enableContextMenuItem": { "message": "Rādīt konteksta izvēlnes iespējas" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Izgūt no" + }, "exportVault": { "message": "Izgūt glabātavas saturu" }, "fileFormat": { "message": "Datnes veids" }, + "fileEncryptedExportWarningDesc": { + "message": "Šī datņu izgūšana būs aizsargāta ar paroli, un būs nepieciešama datnes parole, lai to atšifrētu." + }, + "filePassword": { + "message": "Datnes parole" + }, + "exportPasswordDescription": { + "message": "Šī parole tiks izmantota, lai izgūtu un ievietotu šo datni" + }, + "accountRestrictedOptionDescription": { + "message": "Jāizmanto konta šifrēšanas atslēga, kas iegūta no lietotājvārda un galvenās paroles, lai šifrētu izguvi un atļautu ievietošanu tikai pašreizējā Bitwarden kontā." + }, + "passwordProtectedOptionDescription": { + "message": "Uzstādīt paroli, lai šifrētu izguvi un tad to ievietotu jebkurā Bitwarden kontā, izmantojot atšifrēšanas paroli." + }, + "exportTypeHeading": { + "message": "Izgūšanas veids" + }, + "accountRestricted": { + "message": "Konts ir ierobežots" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Datnes parole\" un \"Apstiprināt datnes paroli\" vērtības nesakrīt." + }, "warning": { "message": "UZMANĪBU", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -807,7 +858,7 @@ "message": "Apstiprināt glabātavas satura izgūšanu" }, "exportWarningDesc": { - "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Izdzēst to uzreiz pēc izmantošanas." + "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izgūto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas." }, "encExportKeyWarningDesc": { "message": "Šī izguve šifrē datus ar konta šifrēšanas atslēgu. Ja tā jebkad tiks mainīta, izvadi vajadzētu veikt vēlreiz, jo vairs nebūs iespējams atšifrēt šo datni." @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Norādīt pašuzstādīta Bitwarden pamata URL." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Pielāgota vide" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Krājumi" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Izlase" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Vienums atjaunots" }, + "alreadyHaveAccount": { + "message": "Jau ir konts?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Atteikšanās noņems piekļuvi glabātavai un pieprasīs tiešsaistes pieteikšanos pēc noildzes laika. Vai tiešām izmantot šo iestatījumu?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automātiski aizpildīt un saglabāt" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Automātiski aizpildīts vienums un saglabāts URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Labi" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Darbvirsmas sinhronizācijas apstiprinājums" }, @@ -2162,7 +2243,7 @@ "message": "Sesijai iestājās noildze. Lūgums mēģināt pieteikties vēlreiz." }, "exportingPersonalVaultTitle": { - "message": "Izdod personīgo glabātavu" + "message": "Izgūst personīgo glabātavu" }, "exportingIndividualVaultDescription": { "message": "Tiks izgūti tikai atsevišķi glabātavas vienumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti. Tiks izgūta tikai glabātavas vienumu informācija, un saistītie pielikumi netiks iekļauti.", @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Izgūst apvienības glabātavu" + }, + "exportingOrganizationVaultDesc": { + "message": "Tiks izgūta tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Kļūda" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Ir nepieciešams apvienības SSO identifikators." }, + "creatingAccountOn": { + "message": "Tiek veidots konts" + }, + "checkYourEmail": { + "message": "Jāpārbauda e-pasts" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Jāatver saite, kas tika nosūtīta uz e-pasta adresi" + }, + "andContinueCreatingYourAccount": { + "message": "un jāturpina sava konta izveide." + }, + "noEmail": { + "message": "Nav e-pasta?" + }, + "goBack": { + "message": "Atgriezties" + }, + "toEditYourEmailAddress": { + "message": ", lai labotu savu e-pasta adresi." + }, "eu": { "message": "ES", "description": "European Union" @@ -2877,7 +2991,7 @@ "message": "Kļūda izguves datnes atšifrēšanā. Izmantotā atslēga neatbilst tai, kas tika izmantota satura izgūšanai." }, "invalidFilePassword": { - "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izdošanas datnes izveidošanas brīdī." + "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izgūšanas datnes izveidošanas brīdī." }, "importDestination": { "message": "Ievietošanas galamērķis" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "Jānotīra atlases vērtības vai jāmēģina cits meklēšanas vaicājums" }, - "copyInfo": { - "message": "Kopēt informāciju, $ITEMNAME$", + "copyInfoLabel": { + "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "Vairāk iespēju, $ITEMNAME$", + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "pārvaldības konsolē," }, @@ -3230,7 +3406,7 @@ "message": "Kļūda mērķa mapes piešķiršanā." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Skatīt $NAME$ vienumus", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Atgriezties uz $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Jauns" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Noņemt $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 24235587d0d..7e578136330 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "പ്രിയങ്കരം" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "കുറിപ്പുകൾ" }, @@ -404,6 +419,9 @@ "launch": { "message": "തുറക്കുക" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "വെബ്സൈറ്റ്" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "ലോഗേഡ് ഔട്ട്" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "നിങ്ങളുടെ പ്രവർത്തന സമയം കഴിഞ്ഞിരിക്കുന്നു." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "വാൾട് എക്സ്പോർട്" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "മുന്നറിയിപ്പ്", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "തങ്ങളുടെ പരിസരത്ത് ചെയ്യുന്ന ബിറ്റ് വാർഡൻ ഇൻസ്റ്റാളേഷന്റെ അടിസ്ഥാന URL വ്യക്തമാക്കുക." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "കളക്ഷൻസ്" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "പ്രിയങ്കരങ്ങള്‍" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "വീണ്ടെടുത്ത ഇനം" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "ലോഗൗട്ട് ചെയ്യുകയാണെങ്കിൽ തങ്ങളുടെ വാൾട്ടിലേക്കുള്ള എല്ലാ ആക്സസും നീക്കംചെയ്യും. കാലയളവിനുശേഷം ഓൺലൈൻ ഓതന്റിക്കേറ്റർ ആവശ്യമാണ്. ഈ ക്രമീകരണം ഉപയോഗിക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "യാന്ത്രികമായി പൂരിപ്പിച്ച് സംരക്ഷിക്കുക" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "യാന്ത്രികമായി പൂരിപ്പിച്ച ഇനവും സംരക്ഷിച്ച URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index dc0edb74e58..4bbc32ef565 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "आवडते" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "टिप" }, @@ -404,6 +419,9 @@ "launch": { "message": "उघडा" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "संकेतस्थळ" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 92e8ff47c25..7552c982967 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favoritt" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notater" }, @@ -404,6 +419,9 @@ "launch": { "message": "Åpne" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Nettsted" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logget av" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Lås opp" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Vis alternativer for kontekstmeny" }, @@ -793,12 +817,39 @@ "message": "Solarisert mørk", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Eksporter hvelvet" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "ADVARSEL", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Spesifiser grunn-nettadressen til din selvbetjente Bitwarden-installasjon." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Samlinger" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritter" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Gjenopprettet objekt" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Autofyll og lagre" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Autoutfylt objekt og lagret URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verifisering av skrivebordssynkronisering" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Feil" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index b69928f663f..62a418d0245 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -173,13 +173,19 @@ "message": "Doorgaan naar web-app?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Ontdek meer functies van je Bitwarden-account in de webapp." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Doorgaan naar Helpcentrum?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Leer meer over het gebruik van Bitwarden in het Helpcentrum." + }, + "continueToBrowserExtensionStore": { + "message": "Doorgaan naar de app store van browserextensies?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help anderen met beslissen of Bitwarden iets voor hen is. Bezoek de app store voor extensies en laat een beoordeling achter." }, "changeMasterPasswordOnWebConfirmation": { "message": "Je kunt je hoofdwachtwoord wijzigen in de Bitwarden-webapp." @@ -199,43 +205,43 @@ "message": "Uitloggen" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Over Bitwarden" }, "about": { "message": "Over" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Meer van Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Doorgaan naar bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden voor Business" }, "bitwardenAuthenticator": { "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": "Met Bitwarden Authenticator kunt je authenticatiesleutels opslaan en TOTP-codes voor tweestapsaanmelding genereren. Lees meer op de website bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Ontwikkelaars kunnen geheimen veilig opslaan, beheren en delen met Bitwarden Secrets Manager. Lees meer op de website 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": "Maak vlotte en veilige inlogervaringen, vrij van traditionele wachtwoorden, met Passwordless.dev. Meer informatie op de website bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Gratis Bitwarden Families" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Je komt in aanmerking voor gratis Bitwarden Families. Claim deze aanbieding vandaag in de webapp." }, "version": { "message": "Versie" @@ -296,7 +302,7 @@ "message": "Automatisch sterke, unieke wachtwoorden voor je logins genereren." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden webapp" }, "importItems": { "message": "Items importeren" @@ -383,6 +389,15 @@ "favorite": { "message": "Favoriet" }, + "unfavorite": { + "message": "Ontfavoriet" + }, + "itemAddedToFavorites": { + "message": "Item toegevoegd aan favorieten" + }, + "itemRemovedFromFavorites": { + "message": "Item verwijderd uit favorieten" + }, "notes": { "message": "Notities" }, @@ -404,6 +419,9 @@ "launch": { "message": "Starten" }, + "launchWebsite": { + "message": "Website openen" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Uitgelogd" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Je inlogsessie is verlopen." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Ontgrendelen" }, + "additionalOptions": { + "message": "Extra instellingen" + }, "enableContextMenuItem": { "message": "Contextmenu-opties weergeven" }, @@ -793,12 +817,39 @@ "message": "Overbelicht donker", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exporteren vanuit" + }, "exportVault": { "message": "Kluis exporteren" }, "fileFormat": { "message": "Bestandsindeling" }, + "fileEncryptedExportWarningDesc": { + "message": "We beveiligen deze bestandsexport met een wachtwoord beveiligd, je hebt het bestandswachtwoord nodig om het te decoderen." + }, + "filePassword": { + "message": "Bestandswachtwoord" + }, + "exportPasswordDescription": { + "message": "We gebruiken dit wachtwoord bij het exporteren en importeren van dit bestand" + }, + "accountRestrictedOptionDescription": { + "message": "Gebruik de encryptiesleutel van je account, afgeleid van je gebruikersnaam en hoodfwachtwoord, om de export te versleutelen en importeren te beperken tot het huidige Bitwarden-account." + }, + "passwordProtectedOptionDescription": { + "message": "Stel een bestandswachtwoord in om de export te versleutelen en te importeren naar een willekeurig Bitwarden-account met het wachtwoord voor decoderen." + }, + "exportTypeHeading": { + "message": "Exporttype" + }, + "accountRestricted": { + "message": "Account beperkt" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Bestandswachtwoord\" en \"Bestandswachtwoord bevestigen\" komen niet overeen." + }, "warning": { "message": "WAARSCHUWING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Gedeeld" }, "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": "Met Bitwarden voor Business kun je je kluis-items met anderen delen door gebruik te maken van een organisatie. Lees meer op de website bitwarden.com." }, "moveToOrganization": { "message": "Naar organisatie verplaatsen" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Geef de basis-URL van jouw zelfgehoste Bitwarden-installatie." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Aangepaste omgeving" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Verzamelingen" }, + "nCollections": { + "message": "$COUNT$ collecties", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorieten" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Hersteld item" }, + "alreadyHaveAccount": { + "message": "Heb je al een account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Uitloggen ontneemt je de toegang tot je kluis en vereist online authenticatie na een periode van time-out. Weet je zeker dat je deze instelling wilt gebruiken?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automatisch invullen en opslaan" }, + "fillAndSave": { + "message": "Invullen en opslaan" + }, "autoFillSuccessAndSavedUri": { "message": "Automatisch gevuld item en opgeslagen URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktopsynchronisatieverificatie" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Organisatiekluis exporteren" + }, + "exportingOrganizationVaultDesc": { + "message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items of items van andere organisaties.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Fout" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organisatie SSO-identificatie vereist." }, + "creatingAccountOn": { + "message": "Account maken bij" + }, + "checkYourEmail": { + "message": "Check je e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Volg de link in de e-mail die verstuurd is naar" + }, + "andContinueCreatingYourAccount": { + "message": "en ga verder met het aanmaken van je account." + }, + "noEmail": { + "message": "Geen e-mail?" + }, + "goBack": { + "message": "Ga terug" + }, + "toEditYourEmailAddress": { + "message": "om je e-mailadres te bewerken." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "Wis filters of probeer een andere zoekterm" }, - "copyInfo": { - "message": "Kopieer info, $ITEMNAME$", + "copyInfoLabel": { + "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "Meer opties, $ITEMNAME$", + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Collecties toewijzen" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3230,7 +3406,7 @@ "message": "Fout bij toewijzen doelmap." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Items weergeven in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Terug naar $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "New" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Verwijder $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items zonder map" + }, + "organizationIsDeactivated": { + "message": "Organisatie is gedeactiveerd" + }, + "contactYourOrgAdmin": { + "message": "Items in een gedeactiveerde organisatie zijn niet toegankelijk. Neem contact op met de eigenaar van je organisatie voor hulp." } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 4ef2e8579c7..aa48d596a42 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Dowiedz się więcej o tym, jak korzystać z centrum pomocy Bitwarden." }, + "continueToBrowserExtensionStore": { + "message": "Kontynuować do sklepu z rozszerzeniami przeglądarki?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Pomóż innym dowiedzieć się, czy Bitwarden jest dla nich odpowiedni. Odwiedź swój sklep z rozszerzeniami do przeglądarki i zostaw ocenę." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Możesz zmienić swoje hasło główne w aplikacji internetowej Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Ulubione" }, + "unfavorite": { + "message": "Usuń z ulubionych" + }, + "itemAddedToFavorites": { + "message": "Element dodany do ulubionych" + }, + "itemRemovedFromFavorites": { + "message": "Element usunięty z ulubionych" + }, "notes": { "message": "Notatki" }, @@ -404,6 +419,9 @@ "launch": { "message": "Uruchom" }, + "launchWebsite": { + "message": "Otwórz stronę" + }, "website": { "message": "Strona" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Wylogowano" }, + "loggedOutDesc": { + "message": "Zostałeś wylogowany z konta." + }, "loginExpired": { "message": "Twoja sesja wygasła." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Odblokuj" }, + "additionalOptions": { + "message": "Dodatkowe opcje" + }, "enableContextMenuItem": { "message": "Pokaż opcje menu kontekstowego" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Eksportuj z" + }, "exportVault": { "message": "Eksportuj sejf" }, "fileFormat": { "message": "Format pliku" }, + "fileEncryptedExportWarningDesc": { + "message": "Plik będzie chroniony hasłem, które będzie wymagane do odszyfrowania pliku." + }, + "filePassword": { + "message": "Hasło do pliku" + }, + "exportPasswordDescription": { + "message": "Hasło będzie używane do eksportowania i importowania pliku" + }, + "accountRestrictedOptionDescription": { + "message": "Użyj klucza szyfrowania konta, pochodzącego z nazwy użytkownika konta i hasła głównego, aby zaszyfrować eksport i ograniczyć import tylko do bieżącego konta Bitwarden." + }, + "passwordProtectedOptionDescription": { + "message": "Ustaw hasło dla pliku, aby zaszyfrować eksport i zaimportować je na dowolne konto Bitwarden przy użyciu hasła do odszyfrowania." + }, + "exportTypeHeading": { + "message": "Rodzaj eksportu" + }, + "accountRestricted": { + "message": "Konto ograniczone" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“Hasło pliku” i “Potwierdź hasło pliku“ nie pasują do siebie." + }, "warning": { "message": "UWAGA", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Wpisz podstawowy adres URL hostowanej instalacji Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Określ bazowy adres URL swojej instalacji Bitwarden. Przykład: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Dla zaawansowanych konfiguracji możesz określić podstawowy adres URL niezależnie dla każdej usługi." + }, + "selfHostedEnvFormInvalid": { + "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." + }, "customEnvironment": { "message": "Niestandardowe środowisko" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Kolekcje" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Ulubione" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Element został przywrócony" }, + "alreadyHaveAccount": { + "message": "Masz już konto?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Po wylogowaniu się z sejfu musisz ponownie zalogować się, aby uzyskać do niego dostęp. Czy na pewno chcesz użyć tego ustawienia?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Automatycznie uzupełnij i zapisz" }, + "fillAndSave": { + "message": "Wypełnij i zapisz" + }, "autoFillSuccessAndSavedUri": { "message": "URI został zapisany i automatycznie uzupełniony" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Błąd podczas odświeżania tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nie znaleziono tokenu odświeżającego ani kluczy API. Spróbuj wylogować się i zalogować ponownie." + }, "desktopSyncVerificationTitle": { "message": "Weryfikacja synchronizacji z aplikacją desktopową" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Eksportowanie sejfu organizacji" + }, + "exportingOrganizationVaultDesc": { + "message": "Tylko sejf organizacji powiązany z $ORGANIZATION$ zostanie wyeksportowany. Pozycje w poszczególnych sejfach lub innych organizacji nie będą uwzględnione.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Błąd" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Identyfikator organizacji jest wymagany." }, + "creatingAccountOn": { + "message": "Tworzenie konta na" + }, + "checkYourEmail": { + "message": "Sprawdź swoją pocztę e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kliknij łącze w wiadomości e-mail wysłanej do" + }, + "andContinueCreatingYourAccount": { + "message": "i kontynuuj tworzenie konta." + }, + "noEmail": { + "message": "Brak wiadomości e-mail?" + }, + "goBack": { + "message": "Wróć" + }, + "toEditYourEmailAddress": { + "message": "aby edytować swój adres e-mail." + }, "eu": { "message": "UE", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Wyczyść filtry lub użyj innej frazy" }, - "copyInfo": { + "copyInfoLabel": { "message": "Skopiuj informacje, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Skopiuj informacje - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Skopiuj notatkę, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Skopiuj notatkę - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Więcej opcji, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Więcej opcji - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Zobacz element - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Przypisz kolekcje" + }, + "copyEmail": { + "message": "Skopiuj e-mail" + }, + "copyPhone": { + "message": "Skopiuj telefon" + }, + "copyAddress": { + "message": "Skopiuj adres" + }, "adminConsole": { "message": "Konsola Administracyjna" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Elementy bez folderu" + }, + "organizationIsDeactivated": { + "message": "Organizacja jest wyłączona" + }, + "contactYourOrgAdmin": { + "message": "Nie można uzyskać dostępu do elementów w wyłączonych organizacjach. Skontaktuj się z właścicielem organizacji, aby uzyskać pomoc." } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 3f1f2aa224d..dd540bd6176 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -173,13 +173,19 @@ "message": "Continuar no aplicativo web?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Explore mais recursos da sua conta no Bitwarden no aplicativo web." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Continuar no Centro de Ajuda?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Saiba mais sobre como usar o Bitwarden no Centro de Ajuda." + }, + "continueToBrowserExtensionStore": { + "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." }, "changeMasterPasswordOnWebConfirmation": { "message": "Você pode alterar a sua senha mestra no aplicativo web Bitwarden." @@ -199,43 +205,43 @@ "message": "Encerrar Sessão" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Sobre o Bitwarden" }, "about": { "message": "Sobre" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Mais do Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Continuar para bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden para Negócios" }, "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 que você armazene as chaves do autenticador e gere códigos TOTP para fluxos de verificação de 2 etapas. Saiba mais no site bitwarden.com" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Gerenciador de Segredos Bitwarden" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Armazene, gerencie e compartilhe segredos de desenvolvedor com o Gerenciador de segredos do Bitwarden. Saiba mais no site 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": "Crie experiências de login suaves e seguras, livres de senhas tradicionais com Passwordless.dev. Saiba mais no site bitwarden.com." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Famílias do Bitwarden Grátis" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Você é elegível para as Famílias do Bitwarden Grátis. Resgate esta oferta hoje no aplicativo web." }, "version": { "message": "Versão" @@ -296,7 +302,7 @@ "message": "Gere automaticamente senhas fortes e únicas para as suas credenciais." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Aplicativo Web Bitwarden" }, "importItems": { "message": "Importar Itens" @@ -383,6 +389,15 @@ "favorite": { "message": "Favorito" }, + "unfavorite": { + "message": "Desfavoritar" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notas" }, @@ -404,6 +419,9 @@ "launch": { "message": "Abrir" }, + "launchWebsite": { + "message": "Abrir site" + }, "website": { "message": "Site" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Sessão encerrada" }, + "loggedOutDesc": { + "message": "Você foi desconectado de sua conta." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Desbloquear" }, + "additionalOptions": { + "message": "Opções adicionais" + }, "enableContextMenuItem": { "message": "Mostrar opções de menu de contexto" }, @@ -793,12 +817,39 @@ "message": "Solarized (escuro)", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportar de" + }, "exportVault": { "message": "Exportar Cofre" }, "fileFormat": { "message": "Formato de arquivo" }, + "fileEncryptedExportWarningDesc": { + "message": "Esta arquivo de exportação será protegido por senha e precisará da mesma para ser descriptografado." + }, + "filePassword": { + "message": "Senha do arquivo" + }, + "exportPasswordDescription": { + "message": "Esta senha será usada para exportar e importar este 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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Compartilhado" }, "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": "O Bitwarden para Business permite que você compartilhe os itens do seu cofre com outras pessoas usando uma organização. Saiba mais no site bitwarden.com." }, "moveToOrganization": { "message": "Mover para a Organização" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifique a URL de base da sua instalação local do Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Ambiente Personalizado" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Coleções" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritos" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item Restaurado" }, + "alreadyHaveAccount": { + "message": "Já tem uma conta?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Sair irá remover todo o acesso ao seu cofre e requer autenticação online após o período de tempo limite. Tem certeza de que deseja usar esta configuração?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Autopreencher e Salvar" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item Auto-Preenchido e URI Salvo" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verificação de sincronização do Desktop" }, @@ -1754,10 +1835,10 @@ "message": "A biometria com o navegador não é suportada neste dispositivo." }, "biometricsNotUnlockedTitle": { - "message": "User locked or logged out" + "message": "Usuário bloqueado ou desconectado" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Por favor, desbloqueie esse usuário no aplicativo da área de trabalho e tente novamente." }, "biometricsFailedTitle": { "message": "Biometria falhou" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Erro" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Identificador SSO da organização é necessário." }, + "creatingAccountOn": { + "message": "Criando conta em" + }, + "checkYourEmail": { + "message": "Verifique seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue criando a sua conta." + }, + "noEmail": { + "message": "Sem e-mail?" + }, + "goBack": { + "message": "Voltar" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "eu": { "message": "Europa", "description": "European Union" @@ -3177,21 +3291,21 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Sugestões de autopreenchimento" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to auto-fill" + "message": "Salvar um item de login para este site autopreenchimento" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Seu cofre está vazio" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Nenhum item corresponde à sua pesquisa" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Limpar filtros ou tentar outro termo de pesquisa" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Painel de administração" }, @@ -3230,7 +3406,7 @@ "message": "Erro ao atribuir pasta de destino." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizar itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Voltar para $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3250,10 +3426,10 @@ } }, "new": { - "message": "New" + "message": "Novo" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Remover $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 32425cb0fea..7fe4400078e 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Saiba mais sobre como utilizar o Bitwarden no Centro de ajuda." }, + "continueToBrowserExtensionStore": { + "message": "Continuar para a loja de extensões do navegador?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Ajude outras pessoas a descobrir se o Bitwarden lhes é adequado. Visite a loja de extensões do seu navegador e deixe uma avaliação agora." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Pode alterar a sua palavra-passe mestra na aplicação Web Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorito" }, + "unfavorite": { + "message": "Remover dos favoritos" + }, + "itemAddedToFavorites": { + "message": "Item adicionado aos favoritos" + }, + "itemRemovedFromFavorites": { + "message": "Item removido dos favoritos" + }, "notes": { "message": "Notas" }, @@ -404,6 +419,9 @@ "launch": { "message": "Iniciar" }, + "launchWebsite": { + "message": "Iniciar site" + }, "website": { "message": "Site" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Sessão terminada" }, + "loggedOutDesc": { + "message": "Foi terminada a sessão da sua conta." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Desbloquear" }, + "additionalOptions": { + "message": "Opções adicionais" + }, "enableContextMenuItem": { "message": "Mostrar opções do menu de contexto" }, @@ -793,12 +817,39 @@ "message": "Solarized (escuro)", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportar de" + }, "exportVault": { "message": "Exportar cofre" }, "fileFormat": { "message": "Formato do ficheiro" }, + "fileEncryptedExportWarningDesc": { + "message": "A exportação deste ficheiro será protegida por uma palavra-passe e exigirá a palavra-passe do ficheiro para ser desencriptada." + }, + "filePassword": { + "message": "Palavra-passe do ficheiro" + }, + "exportPasswordDescription": { + "message": "Esta palavra-passe será utilizada para exportar e importar este ficheiro" + }, + "accountRestrictedOptionDescription": { + "message": "Utilize a chave de encriptação da sua conta, derivada do nome de utilizador e da palavra-passe mestra da sua conta, para encriptar a exportação e restringir a importação apenas à conta Bitwarden atual." + }, + "passwordProtectedOptionDescription": { + "message": "Defina uma palavra-passe do ficheiro para encriptar a exportação e importe-a para qualquer conta Bitwarden utilizando a palavra-passe de desencriptação." + }, + "exportTypeHeading": { + "message": "Tipo de exportação" + }, + "accountRestricted": { + "message": "Conta restringida" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Palavra-passe do ficheiro\" e \"Confirmar palavra-passe do ficheiro\" não correspondem." + }, "warning": { "message": "AVISO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local." }, + "selfHostedBaseUrlHint": { + "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local. Exemplo: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Para uma configuração avançada, pode especificar o URL de base de cada serviço de forma independente." + }, + "selfHostedEnvFormInvalid": { + "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Coleções" }, + "nCollections": { + "message": "$COUNT$ coleções", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoritos" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restaurado" }, + "alreadyHaveAccount": { + "message": "Já tem uma conta?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Ao terminar sessão removerá todo o acesso ao seu cofre e requer autenticação online após o período de tempo limite. Tem a certeza de que pretende utilizar esta definição?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Preencher automaticamente e guardar" }, + "fillAndSave": { + "message": "Preencher e guardar" + }, "autoFillSuccessAndSavedUri": { "message": "Item preenchido automaticamente e URI guardado" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Erro no acesso ao token de atualização" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Não foi encontrado nenhum token de atualização ou chaves API. Por favor, tente terminar a sessão e voltar a iniciá-la." + }, "desktopSyncVerificationTitle": { "message": "Verificação da sincronização da aplicação para computador" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "A exportar o cofre da organização" + }, + "exportingOrganizationVaultDesc": { + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Os itens em cofres individuais ou noutras organizações não serão incluídos.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Erro" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "É necessário o identificador de SSO da organização." }, + "creatingAccountOn": { + "message": "A criar uma conta em" + }, + "checkYourEmail": { + "message": "Verifique o seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue a criação da sua conta." + }, + "noEmail": { + "message": "Não recebeu o e-mail?" + }, + "goBack": { + "message": "Volte atrás" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "eu": { "message": "UE", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Limpe os filtros ou tente outro termo de pesquisa" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copiar informações, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copiar informações - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copiar nota, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copiar nota - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Mais opções, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Mais opções - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Ver item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Atribuir coleções" + }, + "copyEmail": { + "message": "Copiar e-mail" + }, + "copyPhone": { + "message": "Copiar telefone" + }, + "copyAddress": { + "message": "Copiar endereço" + }, "adminConsole": { "message": "Consola de administração" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Itens sem pasta" + }, + "organizationIsDeactivated": { + "message": "A organização está desativada" + }, + "contactYourOrgAdmin": { + "message": "Não é possível aceder aos itens de organizações desativadas. Contacte o proprietário da organização para obter assistência." } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index af09c4b3a93..4fb528a61bf 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorit" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Note" }, @@ -404,6 +419,9 @@ "launch": { "message": "Lansare" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Sait web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Deconectat" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Deblocare" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Afișați opțiunile meniului contextual" }, @@ -793,12 +817,39 @@ "message": "Întuneric solarizat", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export seif" }, "fileFormat": { "message": "Format fișier" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "AVERTISMENT", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specificați URL-ul de bază al implementări Bitwarden găzduită local." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Mediu personalizat" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Colecții" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorite" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Articol restabilit" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "După expirare, accesul la seiful dvs. va fi restricționat și va fi necesară autentificarea online. Sigur doriți să utilizați această setare?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Completare automată și salvare" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Articol completat automat și URI salvat" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verificare sincronizare desktop" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Eroare" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Identificatorul SSO al organizației este necesar." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 1b8d103c0e9..d6dab1dd477 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Подробнее о том, как использовать Bitwarden, можно узнать в справочном центре." }, + "continueToBrowserExtensionStore": { + "message": "Перейти в магазин расширений для браузера?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Помогите другим узнать, подходит ли им Bitwarden. Посетите магазин расширений вашего браузера и оставьте отзыв прямо сейчас." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Изменить мастер-пароль можно в веб-приложении Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Избранный" }, + "unfavorite": { + "message": "Удалить из избранного" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Заметки" }, @@ -404,6 +419,9 @@ "launch": { "message": "Перейти" }, + "launchWebsite": { + "message": "Открыть сайт" + }, "website": { "message": "Сайт" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Вы вышли из хранилища" }, + "loggedOutDesc": { + "message": "Вы вышли из своего аккаунта." + }, "loginExpired": { "message": "Истек срок действия вашего сеанса." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Разблокировать" }, + "additionalOptions": { + "message": "Дополнительные настройки" + }, "enableContextMenuItem": { "message": "Показать опции контекстного меню" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Экспорт из" + }, "exportVault": { "message": "Экспорт хранилища" }, "fileFormat": { "message": "Формат файла" }, + "fileEncryptedExportWarningDesc": { + "message": "Экспорт этого файла будет защищен паролем, и для расшифровки потребуется пароль файла." + }, + "filePassword": { + "message": "Пароль к файлу" + }, + "exportPasswordDescription": { + "message": "Этот пароль будет использоваться для экспорта и импорта этого файла" + }, + "accountRestrictedOptionDescription": { + "message": "Использовать ключ шифрования вашего аккаунта, полученный из имени пользователя и мастер-пароля, для шифрования экспорта и ограничения импорта только для текущего аккаунта Bitwarden." + }, + "passwordProtectedOptionDescription": { + "message": "Установите пароль файла для шифрования экспорта и импортируйте его в любую учетную запись Bitwarden, используя пароль для расшифровки." + }, + "exportTypeHeading": { + "message": "Тип экспорта" + }, + "accountRestricted": { + "message": "Ограничено аккаунтом" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Пароль к файлу\" и \"Подтверждение пароля к файлу\" не совпадают." + }, "warning": { "message": "ВНИМАНИЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Укажите URL Bitwarden на вашем сервере." }, + "selfHostedBaseUrlHint": { + "message": "Укажите базовый URL вашего локального хостинга Bitwarden. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для продвинутой конфигурации можно указать базовый URL каждой службы отдельно." + }, + "selfHostedEnvFormInvalid": { + "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." + }, "customEnvironment": { "message": "Пользовательское окружение" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Коллекции" }, + "nCollections": { + "message": "Коллекций: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Избранные" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Элемент восстановлен" }, + "alreadyHaveAccount": { + "message": "Уже зарегистрированы?" + }, "vaultTimeoutLogOutConfirmation": { "message": "По истечении тайм-аута будет выполнен выход, что приведет к отмене всех прав доступа к вашему хранилищу и потребует онлайн-аутентификации. Вы уверены, что хотите использовать этот параметр?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Заполнить и сохранить" }, + "fillAndSave": { + "message": "Заполнить и сохранить" + }, "autoFillSuccessAndSavedUri": { "message": "URI элемента заполнен и сохранен" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Ошибка обновления токена доступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не найдены токен обновления или ключи API. Пожалуйста, попробуйте выполнить выход и повторно авторизоваться." + }, "desktopSyncVerificationTitle": { "message": "Проверка синхронизации на компьютере" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Экспорт хранилища организации" + }, + "exportingOrganizationVaultDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Элементы из личных хранилищ и из других организаций включены не будут.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Ошибка" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Требуется идентификатор SSO организации." }, + "creatingAccountOn": { + "message": "Создание аккаунта" + }, + "checkYourEmail": { + "message": "Проверьте свою почту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдите по ссылке из отправленного письма" + }, + "andContinueCreatingYourAccount": { + "message": "и продолжите создание аккаунта." + }, + "noEmail": { + "message": "Нет письма?" + }, + "goBack": { + "message": "Вернуться" + }, + "toEditYourEmailAddress": { + "message": "для изменения адреса email." + }, "eu": { "message": "Европа", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Очистите фильтры или попробуйте другой поисковый запрос" }, - "copyInfo": { + "copyInfoLabel": { "message": "Скопировать информацию, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Скопировать информацию - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Скопировать заметку, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Скопировать заметку - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Больше опций, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Больше опций - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Просмотр элемента - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Назначить коллекции" + }, + "copyEmail": { + "message": "Скопировать email" + }, + "copyPhone": { + "message": "Скопировать телефон" + }, + "copyAddress": { + "message": "Скопировать адрес" + }, "adminConsole": { "message": "консоли администратора" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Элементы без папки" + }, + "organizationIsDeactivated": { + "message": "Организация деактивирована" + }, + "contactYourOrgAdmin": { + "message": "Доступ к элементам в деактивированных организациях невозможен. Обратитесь за помощью к владельцу организации." } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 3196fbbe15a..5dfeef73bb4 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "ප්‍රියතමය" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "සටහන්" }, @@ -404,6 +419,9 @@ "launch": { "message": "දියත්කරන්න" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "වියමන අඩවිය" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "ලොගින් වී" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "ඔබගේ පිවිසුම් සැසිය කල් ඉකුත් වී ඇත." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "අඳුරු අඳුරු", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "අපනයන සුරක්ෂිතාගාරය" }, "fileFormat": { "message": "ගොනු ආකෘතිය" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "අවවාදයයි", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "බිට්වර්ඩන් ස්ථාපනය සත්කාරකත්වය දරනු ලබන ඔබගේ පරිශ්රයේ මූලික URL එක සඳහන් කරන්න." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "අභිරුචි පරිසරය" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "එකතුව" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "ප්රියතම දැන්වීම්" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "ප්රතිෂ්ඨාපනය අයිතමය" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "පිටතට පිවිසීමෙන් ඔබගේ සුරක්ෂිතාගාරය වෙත ඇති සියලුම ප්රවේශය ඉවත් කරනු ඇති අතර කාල සීමාව පසු මාර්ගගත සත්යාපනය අවශ්ය වේ. ඔබට මෙම සැකසුම භාවිතා කිරීමට අවශ්ය බව ඔබට විශ්වාසද?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "ස්වයංක්රීය-පිරවීම සහ සුරකින්න" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "ස්වයංක්රීය-පිරවූ අයිතමය සහ සුරකින ලද URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "හරි" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "ඩෙස්ක්ටොප් සමමුහුර්ත සත්යාපනය" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 3716ec18144..2050bd87bd4 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Viac informácií o používaní Bitwardenu nájdete v centre pomoci." }, + "continueToBrowserExtensionStore": { + "message": "Pokračovať do obchodu s rozšíreniami pre prehliadače?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Pomôžte ostatným zistiť, či je Bitwarden pre nich vhodný. Navštívte obchod s rozšíreniami pre prehliadače a pridajte hodnotenie." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Hlavné heslo si môžete zmeniť vo webovej aplikácii Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Obľúbené" }, + "unfavorite": { + "message": "Odstrániť z obľúbených" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Poznámky" }, @@ -404,6 +419,9 @@ "launch": { "message": "Spustiť" }, + "launchWebsite": { + "message": "Otvoriť stránku" + }, "website": { "message": "Webstránka" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Odhlásený" }, + "loggedOutDesc": { + "message": "Boli ste odhlásení zo svojho účtu." + }, "loginExpired": { "message": "Vaša relácia vypršala." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Odomknúť" }, + "additionalOptions": { + "message": "Ďalšie možnosti" + }, "enableContextMenuItem": { "message": "Zobraziť možnosti kontextovej ponuky" }, @@ -793,12 +817,39 @@ "message": "Solarized –⁠ tmavý", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportovať z" + }, "exportVault": { "message": "Export trezoru" }, "fileFormat": { "message": "Formát Súboru" }, + "fileEncryptedExportWarningDesc": { + "message": "Tento exportovaný súbor bude chránený heslom a na dešifrovanie bude potrebné heslo súboru." + }, + "filePassword": { + "message": "Heslo súboru" + }, + "exportPasswordDescription": { + "message": "Toto heslo sa použije na export a import tohto súboru" + }, + "accountRestrictedOptionDescription": { + "message": "Na zašifrovanie exportu a obmedzenie importu len na aktuálny účet Bitwarden použite šifrovací kľúč účtu odvodený z používateľského mena a hlavného hesla účtu." + }, + "passwordProtectedOptionDescription": { + "message": "Nastavte heslo súboru na zašifrovanie exportu a importujte ho do akéhokoľvek účtu Bitwarden pomocou hesla na dešifrovanie." + }, + "exportTypeHeading": { + "message": "Typ exportu" + }, + "accountRestricted": { + "message": "Obmedzený účet" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Heslo súboru\" a \"Potvrdiť heslo súboru\" sa nezhodujú." + }, "warning": { "message": "UPOZORNENIE", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden. Napríklad: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Pre rozšírenú konfiguráciu môžete zadať základnú adresu URL každej služby nezávisle." + }, + "selfHostedEnvFormInvalid": { + "message": "Musíte pridať buď základnú adresu URL servera, alebo aspoň jedno vlastné prostredie." + }, "customEnvironment": { "message": "Vlastné prostredie" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Zbierky" }, + "nCollections": { + "message": "$COUNT$ zbierok", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Obľúbené" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Obnovená položka" }, + "alreadyHaveAccount": { + "message": "Už máte účet?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Odhlásenie bude vyžadovať online prihlásenie po vypršaní časového limitu. Naozaj chcete použiť toto nastavenie?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-vyplniť a Uložiť" }, + "fillAndSave": { + "message": "Vyplniť a uložiť" + }, "autoFillSuccessAndSavedUri": { "message": "Automatické vypĺnenie a uloženie úspešné" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Chyba obnovenia prístupového tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nenašiel sa žiadny token obnovenia ani kľúče API. Skúste sa odhlásiť a znova prihlásiť." + }, "desktopSyncVerificationTitle": { "message": "Overenie synchronizácie desktopu" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exportovanie trezora organizácie" + }, + "exportingOrganizationVaultDesc": { + "message": "Exportované budú iba položky trezora organizácie spojené s $ORGANIZATION$. Položky osobného trezora a položky z iných organizácií nebudú zahrnuté.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Chyba" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Pole identifikátora SSO je povinné." }, + "creatingAccountOn": { + "message": "Vytváranie účtu na" + }, + "checkYourEmail": { + "message": "Skontrolujte si svoj e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Postupujte podľa odkazu v e-maile zaslanom na adresu" + }, + "andContinueCreatingYourAccount": { + "message": "a pokračujte vo vytváraní účtu." + }, + "noEmail": { + "message": "Žiadny email?" + }, + "goBack": { + "message": "Prejsť späť" + }, + "toEditYourEmailAddress": { + "message": "na úpravu e-mailovej adresy." + }, "eu": { "message": "EÚ", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Vymažte filtre alebo zmeňte vyhľadávaný výraz" }, - "copyInfo": { + "copyInfoLabel": { "message": "Skopírovať info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Skopírovať info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Skopírovať poznámku, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Skopírovať poznámku - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Ďalšie možnosti, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Ďalšie možnosti - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Zobrazit položku - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Prideliť zbierky" + }, + "copyEmail": { + "message": "Skopírovať e-mail" + }, + "copyPhone": { + "message": "Skopírovať telefón" + }, + "copyAddress": { + "message": "Skopírovať adresu" + }, "adminConsole": { "message": "Správcovská konzola" }, @@ -3230,7 +3406,7 @@ "message": "Chyba pri priraďovaní cieľového priečinka." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobraziť položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Späť do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Nová" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odstrániť $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Položky bez priečinka" + }, + "organizationIsDeactivated": { + "message": "Organizácia je vypnutá" + }, + "contactYourOrgAdmin": { + "message": "K položkám vo vypnutej organizácii nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 35606e0b108..1f2c35a4bea 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Priljubljeni" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Opombe" }, @@ -404,6 +419,9 @@ "launch": { "message": "Zaženi" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Spletna stran" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Vaša seja je potekla." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Prikaži možnosti kontekstnega menuja" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvoz trezorja" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "OPOZORILO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Okolje po meri" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Zbirke" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Priljubljeno" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Element obnovljen" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Samodejno izpolni in shrani" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Element je bil samodejno izpolnjen in shranjen" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "V redu" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Napaka" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 240a13a785e..7dad0623e12 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -173,13 +173,19 @@ "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": "Learn more about how to use Bitwarden у помоћни центар." + }, + "continueToBrowserExtensionStore": { + "message": "Желите ли да наставите у продавницу додатака за прегледач?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Помозите другима да сазнају да ли је Bitwarden прави за њих. Посетите продавницу апликација и оставите оцену." }, "changeMasterPasswordOnWebConfirmation": { "message": "Можете променити главну лозинку на Bitwarden веб апликацији." @@ -199,16 +205,16 @@ "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" @@ -217,25 +223,25 @@ "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 омогућава вам да чувате кључеве аутентификатора и генеришете ТОТП кодове за токове верификације у 2 корака. Сазнајте више на bitwarden.com" }, "bitwardenSecretsManager": { "message": "Bitwarden Secrets Manager" }, "continueToSecretsManagerPageDesc": { - "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." + "message": "Сигурно чувајте, управљајте и делите тајне програмера са Bitwarden Secrets Manager. Сазнајте више на 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 Families" }, "freeBitwardenFamiliesPageDesc": { - "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." + "message": "Имате право на Бесплатно Bitwarden Families. Искористите ову понуду данас у веб апликацији." }, "version": { "message": "Верзија" @@ -296,7 +302,7 @@ "message": "Аутоматски генеришите јаке, јединствене лозинке за ваше пријаве." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden веб апликација" }, "importItems": { "message": "Увоз ставки" @@ -383,6 +389,15 @@ "favorite": { "message": "Омиљено" }, + "unfavorite": { + "message": "Скини омиљено" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Белешке" }, @@ -404,6 +419,9 @@ "launch": { "message": "Отвори" }, + "launchWebsite": { + "message": "Покрените веб локацију" + }, "website": { "message": "Веб сајт" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Одјављено" }, + "loggedOutDesc": { + "message": "Одјављени сте са свог налога." + }, "loginExpired": { "message": "Ваша сесија је истекла." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Откључај" }, + "additionalOptions": { + "message": "Додатне опције" + }, "enableContextMenuItem": { "message": "Прикажи контекстни мени" }, @@ -793,12 +817,39 @@ "message": "Solarized црно", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Извоз од" + }, "exportVault": { "message": "Извоз сефа" }, "fileFormat": { "message": "Формат датотеке" }, + "fileEncryptedExportWarningDesc": { + "message": "Овај извоз ће бити заштићен лозинком и захтеваће лозинку датотеке за дешифровање." + }, + "filePassword": { + "message": "Лозинка датотеке" + }, + "exportPasswordDescription": { + "message": "Ова лозинка ће се користити за извоз и увоз ове датотеке" + }, + "accountRestrictedOptionDescription": { + "message": "Користите кључ за шифровање вашег налога, изведен из корисничког имена и главне лозинке, да шифрујете извоз и ограничите увоз само на тренутни Bitwarden налог." + }, + "passwordProtectedOptionDescription": { + "message": "Подесите лозинку за шифровање извоза и увоз у било који Bitwarden налог користећи лозинку за дешифровање." + }, + "exportTypeHeading": { + "message": "Тип извоза" + }, + "accountRestricted": { + "message": "Налог је ограничен" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Унете лозинке се не подударају." + }, "warning": { "message": "УПОЗОРЕЊЕ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Дељено" }, "bitwardenForBusinessPageDesc": { - "message": "Bitwarden for Business allows you to share your vault items with others by using an organization. Learn more on the bitwarden.com website." + "message": "Bitwarden for Business вам омогућава да делите ставке сефа са другима користећи организацију. Сазнајте више на веб локацију bitwarden.com." }, "moveToOrganization": { "message": "Премести у организацију" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Наведите основни УРЛ ваше локалне Bitwarden инсталације." }, + "selfHostedBaseUrlHint": { + "message": "Наведите основну УРЛ адресу вашег локалног хостовања Bitwarden-а. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За напредну конфигурацију, можете навести основну УРЛ адресу сваке услуге независно." + }, + "selfHostedEnvFormInvalid": { + "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." + }, "customEnvironment": { "message": "Прилагођено окружење" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Колекције" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Омиљени" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Ставка враћена" }, + "alreadyHaveAccount": { + "message": "Већ имате налог?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Одјава ће уклонити сваки приступ вашем сефу и захтева мрежну потврду идентитета након истека тајмаута. Да ли сте сигурни да желите да користите ову поставку?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Аутоматско попуњавање и чување" }, + "fillAndSave": { + "message": "Попуни и сачувај" + }, "autoFillSuccessAndSavedUri": { "message": "Аутоматски попуњена ставка и сачуван УРЛ" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "У реду" }, + "errorRefreshingAccessToken": { + "message": "Грешка при освежавању токена приступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Није пронађен токен за освежавање или АПИ кључеви. Покушајте да се одјавите и поново пријавите." + }, "desktopSyncVerificationTitle": { "message": "Провера синхронизације Desktop-а" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Извоз сефа организације" + }, + "exportingOrganizationVaultDesc": { + "message": "Биће извезен само сеф организације повезан са $ORGANIZATION$. Ставке у појединачним сефовима или другим организацијама неће бити укључене.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Грешка" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Потребан је SSO идентификатор организације." }, + "creatingAccountOn": { + "message": "Креирај налог на" + }, + "checkYourEmail": { + "message": "Проверите свој имејл" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Пратите везу послатој на" + }, + "andContinueCreatingYourAccount": { + "message": "и наставите са креирањем налога." + }, + "noEmail": { + "message": "Немате имејл?" + }, + "goBack": { + "message": "Ићи назад" + }, + "toEditYourEmailAddress": { + "message": "да измените свој имејл." + }, "eu": { "message": "EU", "description": "European Union" @@ -3177,22 +3291,22 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Предлози за ауто-попуњавање" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to auto-fill" + "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": "Обришите филтере или покушајте са другим термином" }, - "copyInfo": { - "message": "Copy info, $ITEMNAME$", + "copyInfoLabel": { + "message": "Копирај информације, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "More options, $ITEMNAME$", + "copyInfoTitle": { + "message": "Копирај информације - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Копирај Белешку, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Копирај Белешку - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "Више опција, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Више опција - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Преглед ставке - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Додели колекције" + }, + "copyEmail": { + "message": "Копирај имејл" + }, + "copyPhone": { + "message": "Копирај телефон" + }, + "copyAddress": { + "message": "Копирај адресу" + }, "adminConsole": { "message": "Администраторска конзола" }, @@ -3230,7 +3406,7 @@ "message": "Грешка при додељивању циљне фасцикле." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Видети ставке у $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад на $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Ново" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Уклонити $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Ставке без фасцикле" + }, + "organizationIsDeactivated": { + "message": "Организација је деактивирана" + }, + "contactYourOrgAdmin": { + "message": "Није могуће приступити ставкама у деактивираним организацијама. Обратите се власнику ваше организације за помоћ." } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index d6107818684..411c1605ef8 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -3,11 +3,11 @@ "message": "Bitwarden" }, "extName": { - "message": "Bitwarden Password Manager", + "message": "Bitwarden Lösenordshanterare", "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": "Hemma, på jobbet eller på språng säkrar Bitwarden enkelt alla dina lösenord, lösenord och känslig information", "description": "Extension description, MUST be less than 112 characters (Safari restriction)" }, "loginOrCreateNewAccount": { @@ -170,19 +170,25 @@ "message": "Ändra huvudlösenord" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Fortsätt till webbapp?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Utforska fler funktioner i ditt Bitwarden-konto i webbappen." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Fortsätt till Hjälpcenter?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Lär dig mer om hur man använder Bitwarden i hjälpcentret." + }, + "continueToBrowserExtensionStore": { + "message": "Fortsätt till webbläsartilläggsbutiken?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Hjälp andra att ta reda på om Bitwarden är rätt för dem. Besök din webbläsares tilläggsbutik och lämna en recension nu." }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Du kan ändra ditt huvudlösenord i Bitwarden-webbappen." }, "fingerprintPhrase": { "message": "Fingeravtrycksfras", @@ -199,28 +205,28 @@ "message": "Logga ut" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Om Bitwarden" }, "about": { "message": "Om" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Mer från Bitwarden" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "Fortsätt till bitwarden.com?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "Bitwarden för Företag" }, "bitwardenAuthenticator": { "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 låter dig lagra autentiseringsnycklar och generera TOTP-koder för tvåstegsverifieringsflöden. Lär dig mer på bitwarden.com hemsidan" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden Hemlighetshanterare" }, "continueToSecretsManagerPageDesc": { "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." @@ -229,7 +235,7 @@ "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": "Skapa smidiga och säkra inloggningsupplevelser fria från traditionella lösenord med Passwordless.dev. Läs mer på bitwarden.com hemsidan." }, "freeBitwardenFamilies": { "message": "Free Bitwarden Families" @@ -296,7 +302,7 @@ "message": "Skapa starka och unika lösenord automatiskt för dina inloggningar." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden webbapp" }, "importItems": { "message": "Importera objekt" @@ -383,6 +389,15 @@ "favorite": { "message": "Favorit" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Anteckningar" }, @@ -404,6 +419,9 @@ "launch": { "message": "Öppna" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Webbplats" }, @@ -417,7 +435,7 @@ "message": "Annat" }, "unlockMethods": { - "message": "Unlock options" + "message": "Upplåsningsalternativ" }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." @@ -429,7 +447,7 @@ "message": "Session timeout" }, "otherOptions": { - "message": "Other options" + "message": "Andra alternativ" }, "rateExtension": { "message": "Betygsätt tillägget" @@ -582,7 +600,7 @@ "message": "Det går inte att skanna QR-koden från den aktuella webbsidan" }, "totpCaptureSuccess": { - "message": "Authenticator key added" + "message": "Autentiseringsnyckel tillagd" }, "totpCapture": { "message": "Scan authenticator QR code from current webpage" @@ -593,6 +611,9 @@ "loggedOut": { "message": "Utloggad" }, + "loggedOutDesc": { + "message": "Du har blivit utloggad från ditt konto." + }, "loginExpired": { "message": "Din inloggningssession har upphört." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Lås upp" }, + "additionalOptions": { + "message": "Ytterligare alternativ" + }, "enableContextMenuItem": { "message": "Visa alternativ för snabbmenyn" }, @@ -779,7 +803,7 @@ "message": "Ändra programmets färgtema." }, "themeDescAlt": { - "message": "Change the application's color theme. Applies to all logged in accounts." + "message": "Ändra programmets färgtema. Gäller för alla inloggade konton." }, "dark": { "message": "Mörkt", @@ -793,12 +817,39 @@ "message": "Solarized mörk", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Exportera från" + }, "exportVault": { "message": "Exportera valv" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "Fillösenord" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "message": "Set a file password to encrypt the export and import it to any Bitwarden account using the password for decryption." + }, + "exportTypeHeading": { + "message": "Exporttyp" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "VARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Ange bas-URL:en för din \"on-premise\"-hostade Bitwarden-installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Anpassad miljö" }, @@ -1109,7 +1169,7 @@ "message": "Turn off your browser’s built in password manager settings to avoid conflicts." }, "turnOffBrowserBuiltInPasswordManagerSettingsLink": { - "message": "Edit browser settings." + "message": "Redigera webbläsarinställningar." }, "autofillOverlayVisibilityOff": { "message": "Av", @@ -1383,6 +1443,15 @@ "collections": { "message": "Samlingar" }, + "nCollections": { + "message": "$COUNT$ samlingar", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoriter" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Återställde objekt" }, + "alreadyHaveAccount": { + "message": "Har du redan ett konto?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Genom att logga ut upphör all åtkomst till valvet och onlineautentisering krävs efter att tidsgränsen överskridits. Är du säker på att du vill använda denna inställning?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Fyll i automatiskt och spara" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Fyllde i objektet automatiskt och sparade URI:n" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "OK" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Verifiering av synkronisering med skrivbordsprogrammet" }, @@ -1757,7 +1838,7 @@ "message": "User locked or logged out" }, "biometricsNotUnlockedDesc": { - "message": "Please unlock this user in the desktop application and try again." + "message": "Lås upp den här användaren i skrivbordsprogrammet och försök igen." }, "biometricsFailedTitle": { "message": "Biometri misslyckades" @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Fel" }, @@ -2237,11 +2330,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Genererad av Bitwarden.", "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": { @@ -2251,7 +2344,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ogiltig $SERVICENAME$ API-token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2261,7 +2354,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ogiltig $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2535,12 +2628,33 @@ "ssoIdentifierRequired": { "message": "Organisationens SSO-identifierare krävs." }, + "creatingAccountOn": { + "message": "Skapa konto på" + }, + "checkYourEmail": { + "message": "Kolla din e-post" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Följ länken i e-postmeddelandet som skickats till" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "Ingen e-post?" + }, + "goBack": { + "message": "Gå tillbaka" + }, + "toEditYourEmailAddress": { + "message": "för att redigera din e-postadress." + }, "eu": { "message": "EU", "description": "European Union" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Åtkomst nekad. Du har inte behörighet att visa denna sida." }, "general": { "message": "Allmänt" @@ -2570,7 +2684,7 @@ "message": "Användarens e-postadress saknas" }, "deviceTrusted": { - "message": "Device trusted" + "message": "Enhet betrodd" }, "inputRequired": { "message": "Inmatning är obligatoriskt." @@ -2701,7 +2815,7 @@ "description": "Notification message for when an import has completed successfully." }, "dataImportFailed": { - "message": "Error importing. Check console for details.", + "message": "Fel vid importeringen. Kolla konsolen för detaljer.", "description": "Notification message for when an import has failed." }, "importNetworkError": { @@ -2739,7 +2853,7 @@ "description": "Page title in overlay" }, "unlockYourAccountToViewMatchingLogins": { - "message": "Unlock your account to view matching logins", + "message": "Lås upp ditt konto för att visa matchande inloggningar", "description": "Text to display in overlay when the account is locked." }, "unlockAccount": { @@ -2853,7 +2967,7 @@ } }, "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." + "message": "Starta Duo och följ stegen för att slutföra inloggningen." }, "duoRequiredForAccount": { "message": "Duo two-step login is required for your account." @@ -2990,7 +3104,7 @@ "message": "Autentisering krävs för att använda lösennyckel. Verifiera din identitet för att fortsätta." }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "Flerfaktorsautentisering avbruten" }, "noLastPassDataFound": { "message": "Ingen LastPass-data hittades" @@ -3008,7 +3122,7 @@ "message": "Felaktig PIN-kod" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "Flerfaktorsautentisering misslyckades" }, "includeSharedFolders": { "message": "Inkludera delade mappar" @@ -3020,7 +3134,7 @@ "message": "Importerar ditt konto..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "LastPass flerfaktorsautentisering krävs" }, "lastPassMFADesc": { "message": "Ange din engångskod från din autentiseringsapp" @@ -3035,7 +3149,7 @@ "message": "LastPass Huvudlösenord" }, "lastPassAuthRequired": { - "message": "LastPass authentication required" + "message": "LastPass autentisering krävs" }, "awaitingSSO": { "message": "Awaiting SSO authentication" @@ -3081,13 +3195,13 @@ "message": "Account limit reached. Log out of an account to add another." }, "active": { - "message": "active" + "message": "aktiv" }, "locked": { "message": "låst" }, "unlocked": { - "message": "unlocked" + "message": "upplåst" }, "server": { "message": "server" @@ -3114,7 +3228,7 @@ } }, "commonImportFormats": { - "message": "Common formats", + "message": "Vanliga format", "description": "Label indicating the most common import formats" }, "overrideDefaultBrowserAutofillTitle": { @@ -3157,10 +3271,10 @@ "message": "Success" }, "removePasskey": { - "message": "Remove passkey" + "message": "Ta bort passkey" }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Passkey borttagen" }, "unassignedItemsBannerNotice": { "message": "Notice: Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console." @@ -3173,7 +3287,7 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "unassignedItemsBannerCTAPartTwo": { - "message": "to make them visible.", + "message": "för att göra dem synliga.", "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { @@ -3183,15 +3297,15 @@ "message": "Save a login item for this site to auto-fill" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Ditt valv är tomt" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Inga objekt matchar din sökning" }, "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,14 +3355,46 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Tilldela samlingar" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Kopiera telefon" + }, + "copyAddress": { + "message": "Kopiera adress" + }, "adminConsole": { "message": "Admin Console" }, "accountSecurity": { - "message": "Account security" + "message": "Kontosäkerhet" }, "notifications": { - "message": "Notifications" + "message": "Aviseringar" }, "appearance": { "message": "Utseende" @@ -3230,7 +3406,7 @@ "message": "Error assigning target folder." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visa objekt i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tillbaka till $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3250,10 +3426,10 @@ } }, "new": { - "message": "New" + "message": "Ny/Nytt" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Ta bort $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Objekt utan mapp" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index e042785011c..e4b83601fe4 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Favorite" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Notes" }, @@ -404,6 +419,9 @@ "launch": { "message": "Launch" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Website" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Show context menu options" }, @@ -793,12 +817,39 @@ "message": "Solarized dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "WARNING", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Collections" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favorites" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Item restored" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "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?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Auto-fill and save" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Item auto-filled and URI saved" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 03e2f1036bb..27988854f58 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "รายการโปรด" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "โน้ต" }, @@ -404,6 +419,9 @@ "launch": { "message": "เริ่ม" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "เว็บไซต์" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "ออกจากระบบ" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "เซสชันของคุณหมดอายุแล้ว" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Unlock" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "แสดงตัวเลือกเมนูบริบท" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export Vault" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "คำเตือน", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "คอลเลกชัน" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "รายการโปรด" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "คืนค่ารายการแล้ว" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "การออกจากระบบจะลบการเข้าถึงตู้นิรภัยของคุณทั้งหมด และต้องมีการตรวจสอบสิทธิ์ออนไลน์หลังจากหมดเวลา คุณแน่ใจหรือไม่ว่าต้องการใช้การตั้งค่านี้" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "กรอกอัตโนมัติและบันทึก" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "เติมรายการอัตโนมัติและบันทึก URI แล้ว" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "ตกลง" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Desktop sync verification" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Error" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index f62f2759485..56c3beb5105 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -173,13 +173,19 @@ "message": "Web uygulamasına devam edilsin mi?" }, "continueToWebAppDesc": { - "message": "Explore more features of your Bitwarden account on the web app." + "message": "Web uygulamasında Bitwarden hesabınızın diğer özelliklerini keşfedin." }, "continueToHelpCenter": { - "message": "Continue to Help Center?" + "message": "Yardım merkezine gitmek ister misiniz?" }, "continueToHelpCenterDesc": { - "message": "Learn more about how to use Bitwarden on the Help Center." + "message": "Yardım merkezinde Bitwarden kullanımı hakkında daha fazla bilgi alabilirsiniz." + }, + "continueToBrowserExtensionStore": { + "message": "Tarayıcınızın uzantı sitesine gitmek ister misiniz?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Bitwarden'ı başkalarına da tanımak ister misiniz? Tarayıcınızın uzantı mağazasını ziyaret edip Bitwarden'ı değerlendirin." }, "changeMasterPasswordOnWebConfirmation": { "message": "Ana parolanızı Bitwarden web uygulamasında değiştirebilirsiniz." @@ -199,19 +205,19 @@ "message": "Çıkış yap" }, "aboutBitwarden": { - "message": "About Bitwarden" + "message": "Bitwarden hakkında" }, "about": { "message": "Hakkında" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Diğer Bitwarden ürünleri" }, "continueToBitwardenDotCom": { - "message": "Continue to bitwarden.com?" + "message": "bitwarden.com'a gitmek ister misiniz?" }, "bitwardenForBusiness": { - "message": "Bitwarden for Business" + "message": "İşletmeler için Bitwarden" }, "bitwardenAuthenticator": { "message": "Bitwarden Authenticator" @@ -220,7 +226,7 @@ "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" }, "bitwardenSecretsManager": { - "message": "Bitwarden Secrets Manager" + "message": "Bitwarden Sır Yöneticisi" }, "continueToSecretsManagerPageDesc": { "message": "Securely store, manage, and share developer secrets with Bitwarden Secrets Manager. Learn more on the bitwarden.com website." @@ -232,7 +238,7 @@ "message": "Create smooth and secure login experiences free from traditional passwords with Passwordless.dev. Learn more on the bitwarden.com website." }, "freeBitwardenFamilies": { - "message": "Free Bitwarden Families" + "message": "Ücretsiz Bitwarden Aile" }, "freeBitwardenFamiliesPageDesc": { "message": "You are eligible for Free Bitwarden Families. Redeem this offer today in the web app." @@ -296,7 +302,7 @@ "message": "Hesaplarınız için otomatik olarak güçlü, özgün parolalar oluşturun." }, "bitWebVaultApp": { - "message": "Bitwarden web app" + "message": "Bitwarden web uygulaması" }, "importItems": { "message": "Hesapları içe aktar" @@ -383,6 +389,15 @@ "favorite": { "message": "Favori" }, + "unfavorite": { + "message": "Favorilerden kaldır" + }, + "itemAddedToFavorites": { + "message": "Kayıt favorilere eklendi" + }, + "itemRemovedFromFavorites": { + "message": "Kayıt favorilerden silindi" + }, "notes": { "message": "Notlar" }, @@ -404,6 +419,9 @@ "launch": { "message": "Aç" }, + "launchWebsite": { + "message": "Web sitesini aç" + }, "website": { "message": "Web sitesi" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Çıkış yapıldı" }, + "loggedOutDesc": { + "message": "Hesabınızdan çıkış yapıldı." + }, "loginExpired": { "message": "Oturumunuz zaman aşımına uğradı." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Kilidi aç" }, + "additionalOptions": { + "message": "Ek seçenekler" + }, "enableContextMenuItem": { "message": "Bağlam menüsü seçeneklerini göster" }, @@ -793,12 +817,39 @@ "message": "Solarized koyu", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Dışa aktarılacak konum" + }, "exportVault": { "message": "Kasayı dışa aktar" }, "fileFormat": { "message": "Dosya biçimi" }, + "fileEncryptedExportWarningDesc": { + "message": "Dışarı aktarılan bu dosya parola korumalı olacak ve dosyanın çözülmesi için parolayı girmeniz gerekecek." + }, + "filePassword": { + "message": "Dosya parolası" + }, + "exportPasswordDescription": { + "message": "Bu parola, bu dosyayı dışa ve içe aktarmak için kullanılacaktır" + }, + "accountRestrictedOptionDescription": { + "message": "Dışa aktarmayı şifrelemek ve içe aktarmayı yalnızca mevcut Bitwarden hesabıyla kısıtlamak için, hesabınızın kullanıcı adı ve ana parolasından türetilen hesap şifreleme anahtarınızı kullanın." + }, + "passwordProtectedOptionDescription": { + "message": "Dışa aktardığınız dosyayı şifrelemek ve bir Bitwarden hesabına içe aktarmak için kullanacağınız parolayı belirleyin." + }, + "exportTypeHeading": { + "message": "Dışa aktarma türü" + }, + "accountRestricted": { + "message": "Hesap kısıtlı" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Dosya parolası\" ile \"Dosya parolasını onaylayın\" eşleşmiyor." + }, "warning": { "message": "UYARI", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -822,7 +873,7 @@ "message": "Paylaşılan" }, "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": "İşletmeler için Bitwarden'ı kullanarak kasanızdaki kayıtları başkalarıyla paylaşabilirsiniz. Daha fazla bilgi için bitwarden.com sitesini ziyaret edin." }, "moveToOrganization": { "message": "Kuruluşa taşı" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Kurum içinde barındırılan Bitwarden kurulumunuzun taban URL'sini belirtin." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Özel ortam" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Koleksiyonlar" }, + "nCollections": { + "message": "$COUNT$ koleksiyon", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Favoriler" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Kayıt geri yüklendi" }, + "alreadyHaveAccount": { + "message": "Zaten hesabınız var mı?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Çıkış yaptığınızda kasanıza erişiminiz tamamen sonlanacak ve zaman aşımının ardından çevrimiçi kimlik doğrulaması yapmanız gerekecek. Bu ayarı kullanmak istediğinizden emin misiniz?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Otomatik doldur ve kaydet" }, + "fillAndSave": { + "message": "Doldur ve kaydet" + }, "autoFillSuccessAndSavedUri": { "message": "Kayıt otomatik dolduruldu ve URI kaydedildi" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Tamam" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Masaüstü eşitleme doğrulaması" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Kuruluş kasasını dışa aktarma" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Hata" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Kuruluş SSO tanımlayıcısı gereklidir." }, + "creatingAccountOn": { + "message": "Hesap oluşturuluyor:" + }, + "checkYourEmail": { + "message": "E-postanızı kontrol edin" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Hesabınızı oluşturmaya devam etmek için" + }, + "andContinueCreatingYourAccount": { + "message": "adresine gönderdiğimiz e-postadaki bağlantıya tıklayın." + }, + "noEmail": { + "message": "E-posta gelmedi mi?" + }, + "goBack": { + "message": "Geri dönüp" + }, + "toEditYourEmailAddress": { + "message": "e-posta adresinizi düzenleyin." + }, "eu": { "message": "AB", "description": "European Union" @@ -3177,22 +3291,22 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "autofillSuggestions": { - "message": "Auto-fill suggestions" + "message": "Önerileri otomatik doldur" }, "autofillSuggestionsTip": { - "message": "Save a login item for this site to auto-fill" + "message": "Otomatik doldurma için bu siteye ait bir hesap kaydededin" }, "yourVaultIsEmpty": { - "message": "Your vault is empty" + "message": "Kasanız boş" }, "noItemsMatchSearch": { - "message": "No items match your search" + "message": "Aramanızla eşleşen kayıt yok" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "Filtreleri temizleyin veya başka bir arama yapmayı deneyin" }, - "copyInfo": { - "message": "Copy info, $ITEMNAME$", + "copyInfoLabel": { + "message": "Bilgileri kopyala, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "More options, $ITEMNAME$", + "copyInfoTitle": { + "message": "Bilgileri kopyala - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Notu kopyala, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Notu kopyala - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "Diğer seçenekler, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Diğer seçenekler - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Kaydı görüntüle - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Koleksiyon ata" + }, + "copyEmail": { + "message": "E-postayı kopyala" + }, + "copyPhone": { + "message": "Telefonu kopyala" + }, + "copyAddress": { + "message": "Adresi kopyala" + }, "adminConsole": { "message": "Yönetici Konsolu" }, @@ -3230,7 +3406,7 @@ "message": "Hedef klasör atama hatası." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ içindeki kayıtları görüntüle", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "$NAME$ klasörüne dön", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Yeni" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ klasörünü kaldır", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Klasörü olmayan kayıtlar" + }, + "organizationIsDeactivated": { + "message": "Kuruluş pasifleştirilmiş" + }, + "contactYourOrgAdmin": { + "message": "Pasif kuruluşlardaki kayıtlara erişilemez. Destek almak için kuruluş sahibinizle iletişime geçin." } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 687d11df722..845774e81a6 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Дізнайтеся більше про те, як використовувати Bitwarden в довідковому центрі." }, + "continueToBrowserExtensionStore": { + "message": "Перейти до вебмагазину розширень?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Допоможіть іншим дізнатися про Bitwarden. Відвідайте вебмагазин розширень для вашого браузера і поставте оцінку." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Ви можете змінити головний пароль у вебпрограмі Bitwarden." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Обране" }, + "unfavorite": { + "message": "Вилучити з обраного" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Нотатки" }, @@ -404,6 +419,9 @@ "launch": { "message": "Перейти" }, + "launchWebsite": { + "message": "Відкрити вебсайт" + }, "website": { "message": "Вебсайт" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Ви вийшли" }, + "loggedOutDesc": { + "message": "Ви вийшли з облікового запису." + }, "loginExpired": { "message": "Тривалість вашого сеансу завершилась." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Розблокувати" }, + "additionalOptions": { + "message": "Додаткові налаштування" + }, "enableContextMenuItem": { "message": "Показувати в контекстному меню" }, @@ -793,12 +817,39 @@ "message": "Solarized темна", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Експортувати з" + }, "exportVault": { "message": "Експортувати сховище" }, "fileFormat": { "message": "Формат файлу" }, + "fileEncryptedExportWarningDesc": { + "message": "Цей експортований файл буде захищений паролем, який необхідно ввести для його розшифрування." + }, + "filePassword": { + "message": "Пароль файлу" + }, + "exportPasswordDescription": { + "message": "Цей пароль буде використано для експортування та імпортування цього файлу" + }, + "accountRestrictedOptionDescription": { + "message": "Використовуйте ключ шифрування свого облікового запису, створений на основі імені користувача й головного пароля, щоб зашифрувати експортовані дані та обмежити можливість імпортування лише до поточного облікового запису Bitwarden." + }, + "passwordProtectedOptionDescription": { + "message": "Встановіть пароль файлу, щоб зашифрувати експортовані дані та імпортувати до будь-якого облікового запису Bitwarden за допомогою цього пароля." + }, + "exportTypeHeading": { + "message": "Тип експорту" + }, + "accountRestricted": { + "message": "Обмежено обліковим записом" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Пароль файлу та підтвердження пароля відрізняються." + }, "warning": { "message": "ПОПЕРЕДЖЕННЯ", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1069,7 +1120,16 @@ "message": "Середовище власного хостингу" }, "selfHostedEnvironmentFooter": { - "message": "Вкажіть основну URL-адресу Bitwarden, встановленого на локальному хостингу." + "message": "Вкажіть основну URL-адресу локально розміщеного встановлення Bitwarden." + }, + "selfHostedBaseUrlHint": { + "message": "Вкажіть основну URL-адресу вашого локально розміщеного встановлення Bitwarden. Зразок: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для розширеної конфігурації ви можете вказати основну URL-адресу окремо для кожної служби." + }, + "selfHostedEnvFormInvalid": { + "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, "customEnvironment": { "message": "Власне середовище" @@ -1383,6 +1443,15 @@ "collections": { "message": "Збірки" }, + "nCollections": { + "message": "$COUNT$ збірок", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Обране" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Запис відновлено" }, + "alreadyHaveAccount": { + "message": "Вже маєте обліковий запис?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Вихід скасує всі права доступу до вашого сховища і вимагатиме автентифікацію після завершення часу очікування. Ви дійсно хочете використати цей параметр?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Заповнити і зберегти" }, + "fillAndSave": { + "message": "Заповнити й зберегти" + }, "autoFillSuccessAndSavedUri": { "message": "Запис заповнено і збережено" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Помилка оновлення токена доступу" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не знайдено токен оновлення або ключі API. Спробуйте вийти, а потім увійти знову." + }, "desktopSyncVerificationTitle": { "message": "Перевірка синхронізації на комп'ютері" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Експортування сховища організації" + }, + "exportingOrganizationVaultDesc": { + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Елементи особистих сховищ або інших організацій не будуть включені.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Помилка" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Потрібен SSO-ідентифікатор організації." }, + "creatingAccountOn": { + "message": "Створення облікового запису" + }, + "checkYourEmail": { + "message": "Перевірте свою електронну пошту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдіть за посиланням у листі, надісланому на" + }, + "andContinueCreatingYourAccount": { + "message": "і завершіть створення облікового запису." + }, + "noEmail": { + "message": "Не отримали електронного листа?" + }, + "goBack": { + "message": "Поверніться назад" + }, + "toEditYourEmailAddress": { + "message": "і виправте адресу електронної пошти." + }, "eu": { "message": "ЄС", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Скиньте фільтри або спробуйте іншу умову пошуку" }, - "copyInfo": { + "copyInfoLabel": { "message": "Копіювати інформацію, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Копіювати інформацію – $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Копіювати нотатку, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Копіювати нотатку – $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "Інші можливості, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "Інші можливості – $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "Переглянути запис – $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Призначити збірки" + }, + "copyEmail": { + "message": "Копіювати е-пошту" + }, + "copyPhone": { + "message": "Копіювати телефон" + }, + "copyAddress": { + "message": "Копіювати адресу" + }, "adminConsole": { "message": "консолі адміністратора," }, @@ -3230,7 +3406,7 @@ "message": "Помилка призначення цільової теки." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Переглянути записи в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад до $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "Новий" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Вилучити $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Елементи без теки" + }, + "organizationIsDeactivated": { + "message": "Організацію деактивовано" + }, + "contactYourOrgAdmin": { + "message": "Елементи в деактивованих організаціях недоступні. Зверніться до власника вашої організації для отримання допомоги." } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 54913730736..bb19edc420f 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "Bạn có thể thay đổi mật khẩu chính của mình trên Bitwarden bản web." }, @@ -383,6 +389,15 @@ "favorite": { "message": "Yêu thích" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "Ghi chú" }, @@ -404,6 +419,9 @@ "launch": { "message": "Khởi chạy" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "Trang web" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "Đã đăng xuất" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "Mở khóa" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "Hiển thị tuỳ chọn menu ngữ cảnh" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Xuất kho lưu trữ" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "CẢNH BÁO", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "Chỉ định liên kết cơ bản của cài đặt bitwarden tại chỗ của bạn." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Môi trường tùy chỉnh" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "Bộ sưu tập" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "Yêu thích" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "Mục đã được khôi phục" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "Việc đăng xuất sẽ loại bỏ tất cả truy cập vào kho lưu trữ của bạn và yêu cầu xác minh trực tuyến sau khi hết giai đoạn thời gian chờ. Bạn có chắc chắn muốn dùng cài đặt này không?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "Tự động điền và Lưu" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "Đã tự động điền mục và lưu URI" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "Ok" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "Xác minh đồng bộ máy tính" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "Lỗi" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "Organization SSO identifier is required." }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Bảng điều khiển dành cho quản trị viên" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 7d5ef53fdd0..4232403aa32 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "访问帮助中心了解更多如何使用 Bitwarden 的信息。" }, + "continueToBrowserExtensionStore": { + "message": "前往浏览器扩展商店吗?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "帮助别人了解 Bitwarden 是否适合他们。立即访问浏览器的扩展程序商店并留下评分。" + }, "changeMasterPasswordOnWebConfirmation": { "message": "您可以在 Bitwarden 网页应用上更改您的主密码。" }, @@ -229,7 +235,7 @@ "message": "Passwordless.dev" }, "continueToPasswordlessDotDevPageDesc": { - "message": "使用 Passwordless.dev 摆脱传统密码,创建流畅且安全的登录体验。访问 bitwarden.com 网站了解更多信息。" + "message": "使用 Passwordless.dev 摆脱传统密码束缚,打造流畅且安全的登录体验。访问 bitwarden.com 网站了解更多信息。" }, "freeBitwardenFamilies": { "message": "免费 Bitwarden 家庭" @@ -383,6 +389,15 @@ "favorite": { "message": "收藏" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "备注" }, @@ -404,6 +419,9 @@ "launch": { "message": "前往" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "网站" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "已注销" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登录会话已过期。" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "解锁​​​​" }, + "additionalOptions": { + "message": "附加选项" + }, "enableContextMenuItem": { "message": "显示上下文菜单选项" }, @@ -793,12 +817,39 @@ "message": "过曝暗", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "导出自" + }, "exportVault": { "message": "导出密码库" }, "fileFormat": { "message": "文件格式" }, + "fileEncryptedExportWarningDesc": { + "message": "此文件导出将受密码保护,需要文件密码才能解密。" + }, + "filePassword": { + "message": "文件密码" + }, + "exportPasswordDescription": { + "message": "此密码将用于导出和导入此文件" + }, + "accountRestrictedOptionDescription": { + "message": "使用衍生自您账户的用户名和主密码的账户加密密钥,以加密此导出并限制只能导入到当前的 Bitwarden 账户。" + }, + "passwordProtectedOptionDescription": { + "message": "设置一个文件密码用来加密此导出,并使用此密码解密以导入到任意 Bitwarden 账户。" + }, + "exportTypeHeading": { + "message": "导出类型" + }, + "accountRestricted": { + "message": "账户受限" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "「文件密码」与「确认文件密码」不一致。" + }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "指定您本地托管的 Bitwarden 安装的基础 URL。" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "自定义环境" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "集合" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "收藏夹" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "项目已恢复" }, + "alreadyHaveAccount": { + "message": "已经拥有账户了吗?" + }, "vaultTimeoutLogOutConfirmation": { "message": "超时后注销账户将解除对密码库的所有访问权限,并需要进行在线身份验证。确定使用此设置吗?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "自动填充并保存" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "项目已自动填充且 URI 已保存" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "确定" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "桌面同步验证" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "正在导出组织密码库" + }, + "exportingOrganizationVaultDesc": { + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库数据。不包括个人密码库和其他组织中的项目。", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "错误" }, @@ -2398,7 +2491,7 @@ "message": "正登录为" }, "notYou": { - "message": "不是你?" + "message": "不是您吗?" }, "newAroundHere": { "message": "初来乍到吗?" @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "必须填写组织 SSO 标识符。" }, + "creatingAccountOn": { + "message": "创建账户于" + }, + "checkYourEmail": { + "message": "检查您的电子邮箱" + }, + "followTheLinkInTheEmailSentTo": { + "message": "点击发送到电子邮件中的链接" + }, + "andContinueCreatingYourAccount": { + "message": "然后继续创建您的账户。" + }, + "noEmail": { + "message": "没收到电子邮件吗?" + }, + "goBack": { + "message": "返回" + }, + "toEditYourEmailAddress": { + "message": "编辑您的电子邮件地址。" + }, "eu": { "message": "欧盟", "description": "European Union" @@ -2747,7 +2861,7 @@ "description": "Button text to display in overlay when the account is locked." }, "fillCredentialsFor": { - "message": "填写凭据", + "message": "为其填写凭据", "description": "Screen reader text for when overlay item is in focused" }, "partialUsername": { @@ -2823,7 +2937,7 @@ "message": "无法完成生物识别。" }, "needADifferentMethod": { - "message": "需要一个不同的方法?" + "message": "尝试其他方式吗?" }, "useMasterPassword": { "message": "使用主密码" @@ -3054,7 +3168,7 @@ "message": "从 CSV 导入" }, "lastPassTryAgainCheckEmail": { - "message": "请重试或查找来自 LastPass 的电子邮件以验证您的身份。" + "message": "请重试或查找来自 LastPass 的可以验证您的身份的电子邮件。" }, "collection": { "message": "集合" @@ -3191,8 +3305,8 @@ "clearFiltersOrTryAnother": { "message": "清除筛选器或尝试另一个搜索词" }, - "copyInfo": { - "message": "复制信息,$ITEMNAME$", + "copyInfoLabel": { + "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { "itemname": { @@ -3201,8 +3315,38 @@ } } }, - "moreOptions": { - "message": "更多选项,$ITEMNAME$", + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { + "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { "itemname": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "管理控制台" }, @@ -3230,7 +3406,7 @@ "message": "分配目标文件夹时出错。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "查看 $NAME$ 中的项目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -3240,7 +3416,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "返回 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -3253,7 +3429,7 @@ "message": "新建" }, "removeItem": { - "message": "Remove $NAME$", + "message": "删除 $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "组织已停用" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index bbb5352ad13..7d00a68d88b 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -181,6 +181,12 @@ "continueToHelpCenterDesc": { "message": "Learn more about how to use Bitwarden on the Help Center." }, + "continueToBrowserExtensionStore": { + "message": "Continue to browser extension store?" + }, + "continueToBrowserExtensionStoreDesc": { + "message": "Help others find out if Bitwarden is right for them. Visit your browser's extension store and leave a rating now." + }, "changeMasterPasswordOnWebConfirmation": { "message": "You can change your master password on the Bitwarden web app." }, @@ -383,6 +389,15 @@ "favorite": { "message": "我的最愛" }, + "unfavorite": { + "message": "Unfavorite" + }, + "itemAddedToFavorites": { + "message": "Item added to favorites" + }, + "itemRemovedFromFavorites": { + "message": "Item removed from favorites" + }, "notes": { "message": "備註" }, @@ -404,6 +419,9 @@ "launch": { "message": "前往" }, + "launchWebsite": { + "message": "Launch website" + }, "website": { "message": "網站" }, @@ -593,6 +611,9 @@ "loggedOut": { "message": "已登出" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登入階段已過期。" }, @@ -756,6 +777,9 @@ "notificationUnlock": { "message": "解鎖" }, + "additionalOptions": { + "message": "Additional options" + }, "enableContextMenuItem": { "message": "顯示內容選單選項" }, @@ -793,12 +817,39 @@ "message": "Solarized Dark 主題", "description": "'Solarized' is a noun and the name of a color scheme. It should not be translated." }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "匯出密碼庫" }, "fileFormat": { "message": "檔案格式" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "warning": { "message": "警告", "description": "WARNING (should stay in capitalized letters if the language permits)" @@ -1071,6 +1122,15 @@ "selfHostedEnvironmentFooter": { "message": "指定您內部部署的 Bitwarden 安裝之基礎 URL。" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "自訂環境" }, @@ -1383,6 +1443,15 @@ "collections": { "message": "集合" }, + "nCollections": { + "message": "$COUNT$ collections", + "placeholders": { + "count": { + "content": "$1", + "example": "2" + } + } + }, "favorites": { "message": "我的最愛" }, @@ -1600,6 +1669,9 @@ "restoredItem": { "message": "項目已還原" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "vaultTimeoutLogOutConfirmation": { "message": "選擇登出將會在密碼庫逾時後移除對密碼庫的所有存取權限,若要重新驗證則需連線網路。確定要使用此設定嗎?" }, @@ -1609,6 +1681,9 @@ "autoFillAndSave": { "message": "自動填入並儲存" }, + "fillAndSave": { + "message": "Fill and save" + }, "autoFillSuccessAndSavedUri": { "message": "項目已自動填入並且 URL 已儲存" }, @@ -1705,6 +1780,12 @@ "ok": { "message": "確定" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "desktopSyncVerificationTitle": { "message": "桌面同步驗證" }, @@ -2173,6 +2254,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "error": { "message": "錯誤" }, @@ -2535,6 +2628,27 @@ "ssoIdentifierRequired": { "message": "需要組織 SSO 識別碼。" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "eu": { "message": "EU", "description": "European Union" @@ -3191,7 +3305,7 @@ "clearFiltersOrTryAnother": { "message": "Clear filters or try another search term" }, - "copyInfo": { + "copyInfoLabel": { "message": "Copy info, $ITEMNAME$", "description": "Aria label for a button that opens a menu with options to copy information from an item.", "placeholders": { @@ -3201,7 +3315,37 @@ } } }, - "moreOptions": { + "copyInfoTitle": { + "message": "Copy info - $ITEMNAME$", + "description": "Title for a button that opens a menu with options to copy information from an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "copyNoteLabel": { + "message": "Copy Note, $ITEMNAME$", + "description": "Aria label for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "copyNoteTitle": { + "message": "Copy Note - $ITEMNAME$", + "description": "Title for a button copies a note to the clipboard.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Note Item" + } + } + }, + "moreOptionsLabel": { "message": "More options, $ITEMNAME$", "description": "Aria label for a button that opens a menu with more options for an item.", "placeholders": { @@ -3211,6 +3355,38 @@ } } }, + "moreOptionsTitle": { + "message": "More options - $ITEMNAME$", + "description": "Title for a button that opens a menu with more options for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "viewItemTitle": { + "message": "View item - $ITEMNAME$", + "description": "Title for a link that opens a view for an item.", + "placeholders": { + "itemname": { + "content": "$1", + "example": "Secret Item" + } + } + }, + "assignCollections": { + "message": "Assign collections" + }, + "copyEmail": { + "message": "Copy email" + }, + "copyPhone": { + "message": "Copy phone" + }, + "copyAddress": { + "message": "Copy address" + }, "adminConsole": { "message": "Admin Console" }, @@ -3261,5 +3437,14 @@ "example": "Work" } } + }, + "itemsWithNoFolder": { + "message": "Items with no folder" + }, + "organizationIsDeactivated": { + "message": "Organization is deactivated" + }, + "contactYourOrgAdmin": { + "message": "Items in deactivated organizations cannot be accessed. Contact your organization owner for assistance." } } diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts index 34bcbcf63ac..5047889b8ec 100644 --- a/apps/browser/src/auth/popup/lock.component.ts +++ b/apps/browser/src/auth/popup/lock.component.ts @@ -25,6 +25,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService } from "@bitwarden/components"; import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors"; @@ -68,6 +69,7 @@ export class LockComponent extends BaseLockComponent { biometricStateService: BiometricStateService, accountService: AccountService, kdfConfigService: KdfConfigService, + syncService: SyncService, ) { super( masterPasswordService, @@ -94,6 +96,7 @@ export class LockComponent extends BaseLockComponent { accountService, authService, kdfConfigService, + syncService, ); this.successRoute = "/tabs/current"; this.isInitialLockScreen = (window as any).previousPopupUrl == null; diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 1ceeed60a99..aa62194af5c 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -42,6 +42,7 @@ type OverlayPortMessage = { type FocusedFieldData = { focusedFieldStyles: Partial; focusedFieldRects: Partial; + tabId?: number; }; type OverlayCipherData = { @@ -66,17 +67,19 @@ type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSende type OverlayBackgroundExtensionMessageHandlers = { [key: string]: CallableFunction; openAutofillOverlay: () => void; - autofillOverlayElementClosed: ({ message }: BackgroundMessageParam) => void; + autofillOverlayElementClosed: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; autofillOverlayAddNewVaultItem: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; getAutofillOverlayVisibility: () => void; checkAutofillOverlayFocused: () => void; focusAutofillOverlayList: () => void; - updateAutofillOverlayPosition: ({ message }: BackgroundMessageParam) => void; + updateAutofillOverlayPosition: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; updateAutofillOverlayHidden: ({ message }: BackgroundMessageParam) => void; - updateFocusedFieldData: ({ message }: BackgroundMessageParam) => void; + updateFocusedFieldData: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; collectPageDetailsResponse: ({ message, sender }: BackgroundOnMessageHandlerParams) => void; unlockCompleted: ({ message }: BackgroundMessageParam) => void; + addedCipher: () => void; addEditCipherSubmitted: () => void; + editedCipher: () => void; deletedCipher: () => void; }; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 45f095aee9f..5598c27dd6e 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -22,7 +22,7 @@ import { NotificationQueueMessageType } from "../enums/notification-queue-messag import { FormData } from "../services/abstractions/autofill.service"; import AutofillService from "../services/autofill.service"; import { createAutofillPageDetailsMock, createChromeTabMock } from "../spec/autofill-mocks"; -import { flushPromises, sendExtensionRuntimeMessage } from "../spec/testing-utils"; +import { flushPromises, sendMockExtensionMessage } from "../spec/testing-utils"; import { AddChangePasswordQueueMessage, @@ -143,7 +143,7 @@ describe("NotificationBackground", () => { const message: NotificationBackgroundExtensionMessage = { command: "unknown" }; jest.spyOn(notificationBackground as any, "handleSaveCipherMessage"); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(notificationBackground["handleSaveCipherMessage"]).not.toHaveBeenCalled(); }); @@ -159,7 +159,7 @@ describe("NotificationBackground", () => { }; jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( @@ -178,7 +178,7 @@ describe("NotificationBackground", () => { }; jest.spyOn(notificationBackground as any, "handleSaveCipherMessage").mockImplementation(); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(notificationBackground["handleSaveCipherMessage"]).toHaveBeenCalledWith( @@ -198,7 +198,7 @@ describe("NotificationBackground", () => { jest.spyOn(notificationBackground as any, "getFolderData"); (firstValueFrom as jest.Mock).mockResolvedValueOnce(folderViews); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(notificationBackground["getFolderData"]).toHaveBeenCalled(); @@ -214,7 +214,7 @@ describe("NotificationBackground", () => { }; jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( @@ -233,7 +233,7 @@ describe("NotificationBackground", () => { }; jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( @@ -281,7 +281,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.LoggedOut); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -296,7 +296,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -312,7 +312,7 @@ describe("NotificationBackground", () => { getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); getEnableAddedLoginPromptSpy.mockReturnValueOnce(false); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -331,7 +331,7 @@ describe("NotificationBackground", () => { getEnableAddedLoginPromptSpy.mockReturnValueOnce(false); getAllDecryptedForUrlSpy.mockResolvedValueOnce([]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -353,7 +353,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "test", password: "oldPassword" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -375,7 +375,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "test", password: "password" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -391,7 +391,7 @@ describe("NotificationBackground", () => { getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); getEnableAddedLoginPromptSpy.mockReturnValueOnce(true); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -411,7 +411,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "anotherTestUsername", password: "password" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -431,7 +431,7 @@ describe("NotificationBackground", () => { }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( @@ -467,7 +467,7 @@ describe("NotificationBackground", () => { data: { newPassword: "newPassword", currentPassword: "currentPassword", url: "" }, }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); @@ -484,7 +484,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -511,7 +511,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "test", password: "password" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -534,7 +534,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "test2", password: "password" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -559,7 +559,7 @@ describe("NotificationBackground", () => { }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( @@ -584,7 +584,7 @@ describe("NotificationBackground", () => { mock({ login: { username: "test2", password: "password" } }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -608,7 +608,7 @@ describe("NotificationBackground", () => { }), ]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( @@ -644,7 +644,7 @@ describe("NotificationBackground", () => { thirdQueueMessage, ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(removeTabFromNotificationQueueSpy).toHaveBeenCalledWith(tab); @@ -677,7 +677,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -744,7 +744,7 @@ describe("NotificationBackground", () => { }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updatePasswordSpy).not.toHaveBeenCalled(); @@ -767,7 +767,7 @@ describe("NotificationBackground", () => { }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updatePasswordSpy).not.toHaveBeenCalled(); @@ -791,7 +791,7 @@ describe("NotificationBackground", () => { }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); expect(updatePasswordSpy).not.toHaveBeenCalled(); expect(editItemSpy).not.toHaveBeenCalled(); expect(createWithServerSpy).not.toHaveBeenCalled(); @@ -815,7 +815,7 @@ describe("NotificationBackground", () => { const cipherView = mock(); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(editItemSpy).not.toHaveBeenCalled(); @@ -854,7 +854,7 @@ describe("NotificationBackground", () => { }); getAllDecryptedForUrlSpy.mockResolvedValueOnce([cipherView]); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updatePasswordSpy).toHaveBeenCalledWith( @@ -887,7 +887,7 @@ describe("NotificationBackground", () => { setAddEditCipherInfoSpy.mockResolvedValue(undefined); openAddEditVaultItemPopoutSpy.mockResolvedValue(undefined); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updatePasswordSpy).toHaveBeenCalledWith( @@ -937,7 +937,7 @@ describe("NotificationBackground", () => { convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updatePasswordSpy).not.toHaveBeenCalled(); @@ -976,7 +976,7 @@ describe("NotificationBackground", () => { convertAddLoginQueueMessageToCipherViewSpy.mockReturnValueOnce(cipherView); editItemSpy.mockResolvedValueOnce(undefined); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(convertAddLoginQueueMessageToCipherViewSpy).toHaveBeenCalledWith( @@ -1019,7 +1019,7 @@ describe("NotificationBackground", () => { throw new Error(errorMessage); }); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(cipherEncryptSpy).toHaveBeenCalledWith(cipherView); @@ -1058,7 +1058,7 @@ describe("NotificationBackground", () => { throw new Error(errorMessage); }); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(updateWithServerSpy).toThrow(errorMessage); @@ -1093,7 +1093,7 @@ describe("NotificationBackground", () => { }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(tabSendMessageDataSpy).not.toHaveBeenCalled(); @@ -1107,7 +1107,7 @@ describe("NotificationBackground", () => { mock({ type: NotificationQueueMessageType.UnlockVault, tab }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(tabSendMessageDataSpy).not.toHaveBeenCalled(); @@ -1125,7 +1125,7 @@ describe("NotificationBackground", () => { }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(tabSendMessageDataSpy).not.toHaveBeenCalled(); @@ -1149,7 +1149,7 @@ describe("NotificationBackground", () => { jest.spyOn(cipherService, "saveNeverDomain").mockImplementation(); jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith(tab, "closeNotificationBar"); @@ -1171,7 +1171,7 @@ describe("NotificationBackground", () => { sender: "not-notificationBar", }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(tabSendMessageDataSpy).not.toHaveBeenCalled(); @@ -1188,7 +1188,7 @@ describe("NotificationBackground", () => { const formData = [mock()]; jest.spyOn(autofillService, "getFormsWithPasswordFields").mockReturnValueOnce(formData); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(tabSendMessageDataSpy).toHaveBeenCalledWith( @@ -1222,7 +1222,7 @@ describe("NotificationBackground", () => { data: { skipNotification: true }, }; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).not.toHaveBeenCalled(); @@ -1237,7 +1237,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.LoggedOut); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -1253,7 +1253,7 @@ describe("NotificationBackground", () => { getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); notificationBackground["notificationQueue"] = [mock()]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(pushUnlockVaultToQueueSpy).not.toHaveBeenCalled(); @@ -1267,7 +1267,7 @@ describe("NotificationBackground", () => { }; getAuthStatusSpy.mockResolvedValueOnce(AuthenticationStatus.Locked); - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(pushUnlockVaultToQueueSpy).toHaveBeenCalledWith("example.com", sender.tab); @@ -1292,7 +1292,7 @@ describe("NotificationBackground", () => { }; notificationBackground["notificationQueue"] = []; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(doNotificationQueueCheckSpy).not.toHaveBeenCalled(); @@ -1309,7 +1309,7 @@ describe("NotificationBackground", () => { mock({ tab: createChromeTabMock({ id: 2 }) }), ]; - sendExtensionRuntimeMessage(message, sender); + sendMockExtensionMessage(message, sender); await flushPromises(); expect(doNotificationQueueCheckSpy).toHaveBeenCalledWith(tab); @@ -1325,7 +1325,7 @@ describe("NotificationBackground", () => { ]; getTabFromCurrentWindowSpy.mockResolvedValueOnce(currenTab); - sendExtensionRuntimeMessage(message, mock({ tab: null })); + sendMockExtensionMessage(message, mock({ tab: null })); await flushPromises(); expect(getTabFromCurrentWindowSpy).toHaveBeenCalledWith(); @@ -1340,7 +1340,7 @@ describe("NotificationBackground", () => { }; const openUnlockWindowSpy = jest.spyOn(notificationBackground as any, "openUnlockPopout"); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(openUnlockWindowSpy).toHaveBeenCalled(); @@ -1363,7 +1363,7 @@ describe("NotificationBackground", () => { .spyOn(environmentService as any, "environment$", "get") .mockReturnValue(new BehaviorSubject(env).asObservable()); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(environmentServiceSpy).toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 9f4da8f21f3..df4867640f4 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -43,7 +43,7 @@ import { createPageDetailMock, createPortSpyMock, } from "../spec/autofill-mocks"; -import { flushPromises, sendExtensionRuntimeMessage, sendPortMessage } from "../spec/testing-utils"; +import { flushPromises, sendMockExtensionMessage, sendPortMessage } from "../spec/testing-utils"; import { AutofillOverlayElement, AutofillOverlayPort, @@ -517,7 +517,7 @@ describe("OverlayBackground", () => { expect(returnValue).toBe(undefined); expect(sendResponse).not.toHaveBeenCalled(); - expect(overlayBackground["overlayElementClosed"]).toHaveBeenCalledWith(message); + expect(overlayBackground["overlayElementClosed"]).toHaveBeenCalledWith(message, sender); }); it("will return a response if the message handler returns a response", async () => { @@ -550,7 +550,7 @@ describe("OverlayBackground", () => { jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(sender.tab); jest.spyOn(BrowserApi, "tabSendMessageData").mockImplementation(); - sendExtensionRuntimeMessage({ command: "openAutofillOverlay" }); + sendMockExtensionMessage({ command: "openAutofillOverlay" }); await flushPromises(); expect(BrowserApi.tabSendMessageData).toHaveBeenCalledWith( @@ -570,8 +570,28 @@ describe("OverlayBackground", () => { await initOverlayElementPorts(); }); + it("disconnects any expired ports if the sender is not from the same page as the most recently focused field", () => { + const port1 = mock(); + const port2 = mock(); + overlayBackground["expiredPorts"] = [port1, port2]; + const sender = mock({ tab: { id: 1 } }); + const focusedFieldData = createFocusedFieldDataMock({ tabId: 2 }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); + + sendMockExtensionMessage( + { + command: "autofillOverlayElementClosed", + overlayElement: AutofillOverlayElement.Button, + }, + sender, + ); + + expect(port1.disconnect).toHaveBeenCalled(); + expect(port2.disconnect).toHaveBeenCalled(); + }); + it("disconnects the button element port", () => { - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "autofillOverlayElementClosed", overlayElement: AutofillOverlayElement.Button, }); @@ -581,7 +601,7 @@ describe("OverlayBackground", () => { }); it("disconnects the list element port", () => { - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "autofillOverlayElementClosed", overlayElement: AutofillOverlayElement.List, }); @@ -602,7 +622,7 @@ describe("OverlayBackground", () => { }); it("will not open the add edit popout window if the message does not have a login cipher provided", () => { - sendExtensionRuntimeMessage({ command: "autofillOverlayAddNewVaultItem" }, sender); + sendMockExtensionMessage({ command: "autofillOverlayAddNewVaultItem" }, sender); expect(overlayBackground["cipherService"].setAddEditCipherInfo).not.toHaveBeenCalled(); expect(overlayBackground["openAddEditVaultItemPopout"]).not.toHaveBeenCalled(); @@ -611,7 +631,7 @@ describe("OverlayBackground", () => { it("will open the add edit popout window after creating a new cipher", async () => { jest.spyOn(BrowserApi, "sendMessage"); - sendExtensionRuntimeMessage( + sendMockExtensionMessage( { command: "autofillOverlayAddNewVaultItem", login: { @@ -641,7 +661,7 @@ describe("OverlayBackground", () => { }); it("will set the overlayVisibility property", async () => { - sendExtensionRuntimeMessage({ command: "getAutofillOverlayVisibility" }); + sendMockExtensionMessage({ command: "getAutofillOverlayVisibility" }); await flushPromises(); expect(await overlayBackground["getOverlayVisibility"]()).toBe( @@ -652,7 +672,7 @@ describe("OverlayBackground", () => { it("returns the overlayVisibility property", async () => { const sendMessageSpy = jest.fn(); - sendExtensionRuntimeMessage( + sendMockExtensionMessage( { command: "getAutofillOverlayVisibility" }, undefined, sendMessageSpy, @@ -669,7 +689,7 @@ describe("OverlayBackground", () => { }); it("will check if the overlay list is focused if the list port is open", () => { - sendExtensionRuntimeMessage({ command: "checkAutofillOverlayFocused" }); + sendMockExtensionMessage({ command: "checkAutofillOverlayFocused" }); expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "checkAutofillOverlayListFocused", @@ -682,7 +702,7 @@ describe("OverlayBackground", () => { it("will check if the overlay button is focused if the list port is not open", () => { overlayBackground["overlayListPort"] = undefined; - sendExtensionRuntimeMessage({ command: "checkAutofillOverlayFocused" }); + sendMockExtensionMessage({ command: "checkAutofillOverlayFocused" }); expect(buttonPortSpy.postMessage).toHaveBeenCalledWith({ command: "checkAutofillOverlayButtonFocused", @@ -697,7 +717,7 @@ describe("OverlayBackground", () => { it("will send a `focusOverlayList` message to the overlay list port", async () => { await initOverlayElementPorts({ initList: true, initButton: false }); - sendExtensionRuntimeMessage({ command: "focusAutofillOverlayList" }); + sendMockExtensionMessage({ command: "focusAutofillOverlayList" }); expect(listPortSpy.postMessage).toHaveBeenCalledWith({ command: "focusOverlayList" }); }); @@ -717,7 +737,24 @@ describe("OverlayBackground", () => { }); it("ignores updating the position if the overlay element type is not provided", () => { - sendExtensionRuntimeMessage({ command: "updateAutofillOverlayPosition" }); + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition" }); + + expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({ + command: "updateIframePosition", + styles: expect.anything(), + }); + expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith({ + command: "updateIframePosition", + styles: expect.anything(), + }); + }); + + it("skips updating the position if the most recently focused field is different than the message sender", () => { + const sender = mock({ tab: { id: 1 } }); + const focusedFieldData = createFocusedFieldDataMock({ tabId: 2 }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); + + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition" }, sender); expect(listPortSpy.postMessage).not.toHaveBeenCalledWith({ command: "updateIframePosition", @@ -731,9 +768,9 @@ describe("OverlayBackground", () => { it("updates the overlay button's position", () => { const focusedFieldData = createFocusedFieldDataMock(); - sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition", overlayElement: AutofillOverlayElement.Button, }); @@ -748,9 +785,9 @@ describe("OverlayBackground", () => { const focusedFieldData = createFocusedFieldDataMock({ focusedFieldRects: { top: 1, left: 2, height: 35, width: 4 }, }); - sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition", overlayElement: AutofillOverlayElement.Button, }); @@ -765,9 +802,9 @@ describe("OverlayBackground", () => { const focusedFieldData = createFocusedFieldDataMock({ focusedFieldRects: { top: 1, left: 2, height: 50, width: 4 }, }); - sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition", overlayElement: AutofillOverlayElement.Button, }); @@ -782,9 +819,9 @@ describe("OverlayBackground", () => { const focusedFieldData = createFocusedFieldDataMock({ focusedFieldStyles: { paddingRight: "20px", paddingLeft: "6px" }, }); - sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition", overlayElement: AutofillOverlayElement.Button, }); @@ -796,13 +833,15 @@ describe("OverlayBackground", () => { }); it("will post a message to the overlay list facilitating an update of the list's position", () => { + const sender = mock({ tab: { id: 1 } }); const focusedFieldData = createFocusedFieldDataMock(); - sendExtensionRuntimeMessage({ command: "updateFocusedFieldData", focusedFieldData }); + sendMockExtensionMessage({ command: "updateFocusedFieldData", focusedFieldData }); - overlayBackground["updateOverlayPosition"]({ - overlayElement: AutofillOverlayElement.List, - }); - sendExtensionRuntimeMessage({ + overlayBackground["updateOverlayPosition"]( + { overlayElement: AutofillOverlayElement.List }, + sender, + ); + sendMockExtensionMessage({ command: "updateAutofillOverlayPosition", overlayElement: AutofillOverlayElement.List, }); @@ -824,7 +863,7 @@ describe("OverlayBackground", () => { command: "updateAutofillOverlayHidden", }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(buttonPortSpy.postMessage).not.toHaveBeenCalledWith(message); expect(listPortSpy.postMessage).not.toHaveBeenCalledWith(message); @@ -833,7 +872,7 @@ describe("OverlayBackground", () => { it("posts a message to the overlay button and list with the display value", () => { const message = { command: "updateAutofillOverlayHidden", display: "none" }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(overlayBackground["overlayButtonPort"].postMessage).toHaveBeenCalledWith({ command: "updateOverlayHidden", @@ -864,7 +903,7 @@ describe("OverlayBackground", () => { }); it("stores the page details provided by the message by the tab id of the sender", () => { - sendExtensionRuntimeMessage( + sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: pageDetails1 }, sender, ); @@ -885,7 +924,7 @@ describe("OverlayBackground", () => { [sender.frameId, { frameId: sender.frameId, tab: sender.tab, details: pageDetails1 }], ]); - sendExtensionRuntimeMessage( + sendMockExtensionMessage( { command: "collectPageDetailsResponse", details: pageDetails2 }, secondFrameSender, ); @@ -928,7 +967,7 @@ describe("OverlayBackground", () => { }, }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -945,7 +984,7 @@ describe("OverlayBackground", () => { }; jest.spyOn(BrowserApi, "getTabFromCurrentWindowId").mockResolvedValueOnce(sender.tab); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(getAuthStatusSpy).toHaveBeenCalled(); @@ -961,29 +1000,23 @@ describe("OverlayBackground", () => { }); }); - describe("addEditCipherSubmitted message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "addEditCipherSubmitted", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); + describe("extension messages that trigger an update of the inline menu ciphers", () => { + const extensionMessages = [ + "addedCipher", + "addEditCipherSubmitted", + "editedCipher", + "deletedCipher", + ]; - sendExtensionRuntimeMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + beforeEach(() => { + jest.spyOn(overlayBackground, "updateOverlayCiphers").mockImplementation(); }); - }); - describe("deletedCipher message handler", () => { - it("updates the overlay ciphers", () => { - const message = { - command: "deletedCipher", - }; - jest.spyOn(overlayBackground as any, "updateOverlayCiphers").mockImplementation(); - - sendExtensionRuntimeMessage(message); - - expect(overlayBackground["updateOverlayCiphers"]).toHaveBeenCalled(); + extensionMessages.forEach((message) => { + it(`triggers an update of the overlay ciphers when the ${message} message is received`, () => { + sendMockExtensionMessage({ command: message }); + expect(overlayBackground.updateOverlayCiphers).toHaveBeenCalled(); + }); }); }); }); @@ -1017,9 +1050,10 @@ describe("OverlayBackground", () => { expect(chrome.runtime.getURL).toHaveBeenCalledWith("overlay/list.css"); expect(overlayBackground["getTranslations"]).toHaveBeenCalled(); expect(overlayBackground["getOverlayCipherData"]).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith({ - overlayElement: AutofillOverlayElement.List, - }); + expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith( + { overlayElement: AutofillOverlayElement.List }, + listPortSpy.sender, + ); }); it("sets up the overlay button port if the port connection is for the overlay button", async () => { @@ -1032,9 +1066,19 @@ describe("OverlayBackground", () => { expect(overlayBackground["getAuthStatus"]).toHaveBeenCalled(); expect(chrome.runtime.getURL).toHaveBeenCalledWith("overlay/button.css"); expect(overlayBackground["getTranslations"]).toHaveBeenCalled(); - expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith({ - overlayElement: AutofillOverlayElement.Button, - }); + expect(overlayBackground["updateOverlayPosition"]).toHaveBeenCalledWith( + { overlayElement: AutofillOverlayElement.Button }, + buttonPortSpy.sender, + ); + }); + + it("stores an existing overlay port so that it can be disconnected at a later time", async () => { + overlayBackground["overlayButtonPort"] = mock(); + + await initOverlayElementPorts({ initList: false, initButton: true }); + await flushPromises(); + + expect(overlayBackground["expiredPorts"].length).toBe(1); }); it("gets the system theme", async () => { diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 0e4abcd82d0..bf954c3419f 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -54,22 +54,27 @@ class OverlayBackground implements OverlayBackgroundInterface { private userAuthStatus: AuthenticationStatus = AuthenticationStatus.LoggedOut; private overlayButtonPort: chrome.runtime.Port; private overlayListPort: chrome.runtime.Port; + private expiredPorts: chrome.runtime.Port[] = []; private focusedFieldData: FocusedFieldData; private overlayPageTranslations: Record; private iconsServerUrl: string; private readonly extensionMessageHandlers: OverlayBackgroundExtensionMessageHandlers = { openAutofillOverlay: () => this.openOverlay(false), - autofillOverlayElementClosed: ({ message }) => this.overlayElementClosed(message), + autofillOverlayElementClosed: ({ message, sender }) => + this.overlayElementClosed(message, sender), autofillOverlayAddNewVaultItem: ({ message, sender }) => this.addNewVaultItem(message, sender), getAutofillOverlayVisibility: () => this.getOverlayVisibility(), checkAutofillOverlayFocused: () => this.checkOverlayFocused(), focusAutofillOverlayList: () => this.focusOverlayList(), - updateAutofillOverlayPosition: ({ message }) => this.updateOverlayPosition(message), + updateAutofillOverlayPosition: ({ message, sender }) => + this.updateOverlayPosition(message, sender), updateAutofillOverlayHidden: ({ message }) => this.updateOverlayHidden(message), - updateFocusedFieldData: ({ message }) => this.setFocusedFieldData(message), + updateFocusedFieldData: ({ message, sender }) => this.setFocusedFieldData(message, sender), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), unlockCompleted: ({ message }) => this.unlockCompleted(message), + addedCipher: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(), + editedCipher: () => this.updateOverlayCiphers(), deletedCipher: () => this.updateOverlayCiphers(), }; private readonly overlayButtonPortMessageHandlers: OverlayButtonPortMessageHandlers = { @@ -302,8 +307,18 @@ class OverlayBackground implements OverlayBackgroundInterface { * the list and button ports and sets them to null. * * @param overlayElement - The overlay element that was closed, either the list or button + * @param sender - The sender of the port message */ - private overlayElementClosed({ overlayElement }: OverlayBackgroundExtensionMessage) { + private overlayElementClosed( + { overlayElement }: OverlayBackgroundExtensionMessage, + sender: chrome.runtime.MessageSender, + ) { + if (sender.tab.id !== this.focusedFieldData?.tabId) { + this.expiredPorts.forEach((port) => port.disconnect()); + this.expiredPorts = []; + return; + } + if (overlayElement === AutofillOverlayElement.Button) { this.overlayButtonPort?.disconnect(); this.overlayButtonPort = null; @@ -320,9 +335,13 @@ class OverlayBackground implements OverlayBackgroundInterface { * is based on the focused field's position and dimensions. * * @param overlayElement - The overlay element to update, either the list or button + * @param sender - The sender of the port message */ - private updateOverlayPosition({ overlayElement }: { overlayElement?: string }) { - if (!overlayElement) { + private updateOverlayPosition( + { overlayElement }: { overlayElement?: string }, + sender: chrome.runtime.MessageSender, + ) { + if (!overlayElement || sender.tab.id !== this.focusedFieldData?.tabId) { return; } @@ -396,9 +415,13 @@ class OverlayBackground implements OverlayBackgroundInterface { * Sets the focused field data to the data passed in the extension message. * * @param focusedFieldData - Contains the rects and styles of the focused field. + * @param sender - The sender of the extension message */ - private setFocusedFieldData({ focusedFieldData }: OverlayBackgroundExtensionMessage) { - this.focusedFieldData = focusedFieldData; + private setFocusedFieldData( + { focusedFieldData }: OverlayBackgroundExtensionMessage, + sender: chrome.runtime.MessageSender, + ) { + this.focusedFieldData = { ...focusedFieldData, tabId: sender.tab.id }; } /** @@ -690,17 +713,11 @@ class OverlayBackground implements OverlayBackgroundInterface { private handlePortOnConnect = async (port: chrome.runtime.Port) => { const isOverlayListPort = port.name === AutofillOverlayPort.List; const isOverlayButtonPort = port.name === AutofillOverlayPort.Button; - if (!isOverlayListPort && !isOverlayButtonPort) { return; } - if (isOverlayListPort) { - this.overlayListPort = port; - } else { - this.overlayButtonPort = port; - } - + this.storeOverlayPort(port); port.onMessage.addListener(this.handleOverlayElementPortMessage); port.postMessage({ command: `initAutofillOverlay${isOverlayListPort ? "List" : "Button"}`, @@ -710,13 +727,47 @@ class OverlayBackground implements OverlayBackgroundInterface { translations: this.getTranslations(), ciphers: isOverlayListPort ? await this.getOverlayCipherData() : null, }); - this.updateOverlayPosition({ - overlayElement: isOverlayListPort - ? AutofillOverlayElement.List - : AutofillOverlayElement.Button, - }); + this.updateOverlayPosition( + { + overlayElement: isOverlayListPort + ? AutofillOverlayElement.List + : AutofillOverlayElement.Button, + }, + port.sender, + ); }; + /** + * Stores the connected overlay port and sets up any existing ports to be disconnected. + * + * @param port - The port to store +| */ + private storeOverlayPort(port: chrome.runtime.Port) { + if (port.name === AutofillOverlayPort.List) { + this.storeExpiredOverlayPort(this.overlayListPort); + this.overlayListPort = port; + return; + } + + if (port.name === AutofillOverlayPort.Button) { + this.storeExpiredOverlayPort(this.overlayButtonPort); + this.overlayButtonPort = port; + } + } + + /** + * When registering a new connection, we want to ensure that the port is disconnected. + * This method places an existing port in the expiredPorts array to be disconnected + * at a later time. + * + * @param port - The port to store in the expiredPorts array + */ + private storeExpiredOverlayPort(port: chrome.runtime.Port | null) { + if (port) { + this.expiredPorts.push(port); + } + } + /** * Handles messages sent to the overlay list or button ports. * diff --git a/apps/browser/src/autofill/content/autofill-init.spec.ts b/apps/browser/src/autofill/content/autofill-init.spec.ts index 13515f6de28..302b520e336 100644 --- a/apps/browser/src/autofill/content/autofill-init.spec.ts +++ b/apps/browser/src/autofill/content/autofill-init.spec.ts @@ -9,7 +9,7 @@ import AutofillOverlayContentService from "../services/autofill-overlay-content. import { flushPromises, mockQuerySelectorAllDefinedCall, - sendExtensionRuntimeMessage, + sendMockExtensionMessage, } from "../spec/testing-utils"; import { RedirectFocusDirection } from "../utils/autofill-overlay.enum"; @@ -144,7 +144,7 @@ describe("AutofillInit", () => { .mockResolvedValue(pageDetails); const response = await autofillInit["handleExtensionMessage"](message, sender, sendResponse); - await Promise.resolve(response); + await flushPromises(); expect(response).toBe(true); expect(sendResponse).toHaveBeenCalledWith(pageDetails); @@ -174,7 +174,7 @@ describe("AutofillInit", () => { .spyOn(autofillInit["collectAutofillContentService"], "getPageDetails") .mockResolvedValue(pageDetails); - sendExtensionRuntimeMessage(message, sender, sendResponse); + sendMockExtensionMessage(message, sender, sendResponse); await flushPromises(); expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ @@ -200,7 +200,7 @@ describe("AutofillInit", () => { .spyOn(autofillInit["collectAutofillContentService"], "getPageDetails") .mockResolvedValue(pageDetails); - sendExtensionRuntimeMessage( + sendMockExtensionMessage( { command: "collectPageDetailsImmediately" }, sender, sendResponse, @@ -233,7 +233,7 @@ describe("AutofillInit", () => { pageDetailsUrl: "https://a-different-url.com", }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(autofillInit["insertAutofillContentService"].fillForm).not.toHaveBeenCalledWith( @@ -242,7 +242,7 @@ describe("AutofillInit", () => { }); it("calls the InsertAutofillContentService to fill the form", async () => { - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "fillForm", fillScript, pageDetailsUrl: window.location.href, @@ -256,7 +256,7 @@ describe("AutofillInit", () => { it("removes the overlay when filling the form", async () => { const blurAndRemoveOverlaySpy = jest.spyOn(autofillInit as any, "blurAndRemoveOverlay"); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "fillForm", fillScript, pageDetailsUrl: window.location.href, @@ -273,7 +273,7 @@ describe("AutofillInit", () => { .spyOn(autofillInit["autofillOverlayContentService"], "focusMostRecentOverlayField") .mockImplementation(); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "fillForm", fillScript, pageDetailsUrl: window.location.href, @@ -297,7 +297,7 @@ describe("AutofillInit", () => { .spyOn(newAutofillInit["insertAutofillContentService"], "fillForm") .mockImplementation(); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "fillForm", fillScript, pageDetailsUrl: window.location.href, @@ -333,13 +333,13 @@ describe("AutofillInit", () => { const newAutofillInit = new AutofillInit(undefined); newAutofillInit.init(); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); }); it("opens the autofill overlay", () => { - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect( autofillInit["autofillOverlayContentService"].openAutofillOverlay, @@ -362,7 +362,7 @@ describe("AutofillInit", () => { newAutofillInit.init(); jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "closeAutofillOverlay", data: { forceCloseOverlay: false }, }); @@ -371,7 +371,7 @@ describe("AutofillInit", () => { }); it("removes the autofill overlay if the message flags a forced closure", () => { - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "closeAutofillOverlay", data: { forceCloseOverlay: true }, }); @@ -384,7 +384,7 @@ describe("AutofillInit", () => { it("ignores the message if a field is currently focused", () => { autofillInit["autofillOverlayContentService"].isFieldCurrentlyFocused = true; - sendExtensionRuntimeMessage({ command: "closeAutofillOverlay" }); + sendMockExtensionMessage({ command: "closeAutofillOverlay" }); expect( autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, @@ -397,7 +397,7 @@ describe("AutofillInit", () => { it("removes the autofill overlay list if the overlay is currently filling", () => { autofillInit["autofillOverlayContentService"].isCurrentlyFilling = true; - sendExtensionRuntimeMessage({ command: "closeAutofillOverlay" }); + sendMockExtensionMessage({ command: "closeAutofillOverlay" }); expect( autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, @@ -408,7 +408,7 @@ describe("AutofillInit", () => { }); it("removes the entire overlay if the overlay is not currently filling", () => { - sendExtensionRuntimeMessage({ command: "closeAutofillOverlay" }); + sendMockExtensionMessage({ command: "closeAutofillOverlay" }); expect( autofillInit["autofillOverlayContentService"].removeAutofillOverlayList, @@ -424,13 +424,13 @@ describe("AutofillInit", () => { const newAutofillInit = new AutofillInit(undefined); newAutofillInit.init(); - sendExtensionRuntimeMessage({ command: "addNewVaultItemFromOverlay" }); + sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" }); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); }); it("will add a new vault item", () => { - sendExtensionRuntimeMessage({ command: "addNewVaultItemFromOverlay" }); + sendMockExtensionMessage({ command: "addNewVaultItemFromOverlay" }); expect(autofillInit["autofillOverlayContentService"].addNewVaultItem).toHaveBeenCalled(); }); @@ -448,13 +448,13 @@ describe("AutofillInit", () => { const newAutofillInit = new AutofillInit(undefined); newAutofillInit.init(); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); }); it("redirects the overlay focus", () => { - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect( autofillInit["autofillOverlayContentService"].redirectOverlayFocusOut, @@ -474,13 +474,13 @@ describe("AutofillInit", () => { const newAutofillInit = new AutofillInit(undefined); newAutofillInit.init(); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); }); it("updates whether the overlay ciphers are populated", () => { - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(autofillInit["autofillOverlayContentService"].isOverlayCiphersPopulated).toEqual( message.data.isOverlayCiphersPopulated, @@ -494,7 +494,7 @@ describe("AutofillInit", () => { newAutofillInit.init(); jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - sendExtensionRuntimeMessage({ command: "bgUnlockPopoutOpened" }); + sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); expect(newAutofillInit["removeAutofillOverlay"]).not.toHaveBeenCalled(); @@ -504,7 +504,7 @@ describe("AutofillInit", () => { jest.spyOn(autofillInit["autofillOverlayContentService"], "blurMostRecentOverlayField"); jest.spyOn(autofillInit as any, "removeAutofillOverlay"); - sendExtensionRuntimeMessage({ command: "bgUnlockPopoutOpened" }); + sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); expect( autofillInit["autofillOverlayContentService"].blurMostRecentOverlayField, @@ -519,7 +519,7 @@ describe("AutofillInit", () => { newAutofillInit.init(); jest.spyOn(newAutofillInit as any, "removeAutofillOverlay"); - sendExtensionRuntimeMessage({ command: "bgVaultItemRepromptPopoutOpened" }); + sendMockExtensionMessage({ command: "bgVaultItemRepromptPopoutOpened" }); expect(newAutofillInit["autofillOverlayContentService"]).toBe(undefined); expect(newAutofillInit["removeAutofillOverlay"]).not.toHaveBeenCalled(); @@ -529,7 +529,7 @@ describe("AutofillInit", () => { jest.spyOn(autofillInit["autofillOverlayContentService"], "blurMostRecentOverlayField"); jest.spyOn(autofillInit as any, "removeAutofillOverlay"); - sendExtensionRuntimeMessage({ command: "bgVaultItemRepromptPopoutOpened" }); + sendMockExtensionMessage({ command: "bgVaultItemRepromptPopoutOpened" }); expect( autofillInit["autofillOverlayContentService"].blurMostRecentOverlayField, @@ -545,7 +545,7 @@ describe("AutofillInit", () => { }); it("skips attempting to update the overlay visibility if the autofillOverlayVisibility data value is not present", () => { - sendExtensionRuntimeMessage({ + sendMockExtensionMessage({ command: "updateAutofillOverlayVisibility", data: {}, }); @@ -563,7 +563,7 @@ describe("AutofillInit", () => { }, }; - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(autofillInit["autofillOverlayContentService"].autofillOverlayVisibility).toEqual( message.data.autofillOverlayVisibility, 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 11880592b6a..226fcb4bd61 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended"; import { VaultOnboardingMessages } from "@bitwarden/common/vault/enums/vault-onboarding.enum"; -import { postWindowMessage, sendExtensionRuntimeMessage } from "../spec/testing-utils"; +import { postWindowMessage, sendMockExtensionMessage } from "../spec/testing-utils"; describe("ContentMessageHandler", () => { const sendMessageSpy = jest.spyOn(chrome.runtime, "sendMessage"); @@ -92,13 +92,13 @@ describe("ContentMessageHandler", () => { describe("handled extension messages", () => { it("ignores the message to the extension background if it is not present in the forwardCommands list", () => { - sendExtensionRuntimeMessage({ command: "someOtherCommand" }); + sendMockExtensionMessage({ command: "someOtherCommand" }); expect(sendMessageSpy).not.toHaveBeenCalled(); }); it("forwards the message to the extension background if it is present in the forwardCommands list", () => { - sendExtensionRuntimeMessage({ command: "bgUnlockPopoutOpened" }); + sendMockExtensionMessage({ command: "bgUnlockPopoutOpened" }); expect(sendMessageSpy).toHaveBeenCalledTimes(1); expect(sendMessageSpy).toHaveBeenCalledWith({ command: "bgUnlockPopoutOpened" }); diff --git a/apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts similarity index 100% rename from apps/browser/src/vault/fido2/background/abstractions/fido2.background.ts rename to apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts diff --git a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts similarity index 97% rename from apps/browser/src/vault/fido2/background/fido2.background.spec.ts rename to apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 5da51618ac5..f0c5fc5695a 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -12,13 +12,13 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault import { createPortSpyMock } from "../../../autofill/spec/autofill-mocks"; import { flushPromises, - sendExtensionRuntimeMessage, + sendMockExtensionMessage, triggerPortOnDisconnectEvent, triggerRuntimeOnConnectEvent, } from "../../../autofill/spec/testing-utils"; import { BrowserApi } from "../../../platform/browser/browser-api"; import { BrowserScriptInjectorService } from "../../../platform/services/browser-script-injector.service"; -import { AbortManager } from "../../background/abort-manager"; +import { AbortManager } from "../../../vault/background/abort-manager"; import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; @@ -263,7 +263,7 @@ describe("Fido2Background", () => { it("ignores messages that do not have a handler associated with a command within the message", () => { const message = mock({ command: "nonexistentCommand" }); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); expect(abortManagerMock.abort).not.toHaveBeenCalled(); }); @@ -274,7 +274,7 @@ describe("Fido2Background", () => { const sendResponse = jest.fn(); fido2ClientService.createCredential.mockRejectedValue(new Error("error")); - sendExtensionRuntimeMessage(message, sender, sendResponse); + sendMockExtensionMessage(message, sender, sendResponse); await flushPromises(); expect(sendResponse).toHaveBeenCalledWith({ error: { message: "error" } }); @@ -287,7 +287,7 @@ describe("Fido2Background", () => { abortedRequestId: "123", }); - sendExtensionRuntimeMessage(message); + sendMockExtensionMessage(message); await flushPromises(); expect(abortManagerMock.abort).toHaveBeenCalledWith(message.abortedRequestId); @@ -302,7 +302,7 @@ describe("Fido2Background", () => { data: mock(), }); - sendExtensionRuntimeMessage(message, senderMock); + sendMockExtensionMessage(message, senderMock); await flushPromises(); expect(fido2ClientService.createCredential).toHaveBeenCalledWith( @@ -323,7 +323,7 @@ describe("Fido2Background", () => { data: mock(), }); - sendExtensionRuntimeMessage(message, senderMock); + sendMockExtensionMessage(message, senderMock); await flushPromises(); expect(fido2ClientService.assertCredential).toHaveBeenCalledWith( diff --git a/apps/browser/src/vault/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts similarity index 99% rename from apps/browser/src/vault/fido2/background/fido2.background.ts rename to apps/browser/src/autofill/fido2/background/fido2.background.ts index 0666f804f28..a58d32da16f 100644 --- a/apps/browser/src/vault/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -13,7 +13,7 @@ import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault import { BrowserApi } from "../../../platform/browser/browser-api"; import { ScriptInjectorService } from "../../../platform/services/abstractions/script-injector.service"; -import { AbortManager } from "../../background/abort-manager"; +import { AbortManager } from "../../../vault/background/abort-manager"; import { Fido2ContentScript, Fido2ContentScriptId } from "../enums/fido2-content-script.enum"; import { Fido2PortName } from "../enums/fido2-port-name.enum"; diff --git a/apps/browser/src/vault/fido2/content/content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts similarity index 95% rename from apps/browser/src/vault/fido2/content/content-script.spec.ts rename to apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index c9f970a30c6..94bef354a79 100644 --- a/apps/browser/src/vault/fido2/content/content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -60,7 +60,7 @@ describe("Fido2 Content Script", () => { chrome.runtime.connect = jest.fn(() => portSpy); it("destroys the messenger when the port is disconnected", () => { - require("./content-script"); + require("./fido2-content-script"); triggerPortOnDisconnectEvent(portSpy); @@ -75,7 +75,7 @@ describe("Fido2 Content Script", () => { const mockResult = { credentialId: "mock" } as CreateCredentialResult; jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); - require("./content-script"); + require("./fido2-content-script"); const response = await messenger.handler!(message, new AbortController()); @@ -99,7 +99,7 @@ describe("Fido2 Content Script", () => { data: mock(), }); - require("./content-script"); + require("./fido2-content-script"); await messenger.handler!(message, new AbortController()); @@ -121,7 +121,7 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); - require("./content-script"); + require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -141,7 +141,7 @@ describe("Fido2 Content Script", () => { abortController.abort(); }); - require("./content-script"); + require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -161,7 +161,7 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); - require("./content-script"); + require("./fido2-content-script"); const result = messenger.handler!(message, abortController); await expect(result).rejects.toEqual(errorMessage); @@ -175,7 +175,7 @@ describe("Fido2 Content Script", () => { contentType: "application/json", })); - require("./content-script"); + require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); }); @@ -193,7 +193,7 @@ describe("Fido2 Content Script", () => { }, })); - require("./content-script"); + require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); }); diff --git a/apps/browser/src/vault/fido2/content/content-script.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.ts similarity index 100% rename from apps/browser/src/vault/fido2/content/content-script.ts rename to apps/browser/src/autofill/fido2/content/fido2-content-script.ts diff --git a/apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts similarity index 93% rename from apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts rename to apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index d40a725a1f4..d53d9e685ed 100644 --- a/apps/browser/src/vault/fido2/content/page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -13,7 +13,7 @@ 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 }); - require("./page-script-append.mv2"); + require("./fido2-page-script-append.mv2"); expect(window.document.createElement).not.toHaveBeenCalled(); }); @@ -24,7 +24,7 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); - require("./page-script-append.mv2"); + require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); @@ -42,7 +42,7 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); - require("./page-script-append.mv2"); + require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); @@ -59,7 +59,7 @@ describe("FIDO2 page-script for manifest v2", () => { return createdScriptElement; }); - require("./page-script-append.mv2"); + require("./fido2-page-script-append.mv2"); jest.spyOn(createdScriptElement, "remove"); createdScriptElement.dispatchEvent(new Event("load")); diff --git a/apps/browser/src/vault/fido2/content/page-script-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts similarity index 100% rename from apps/browser/src/vault/fido2/content/page-script-append.mv2.ts rename to apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts similarity index 99% rename from apps/browser/src/vault/fido2/content/page-script.ts rename to apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 5898dbd04df..cf144713596 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -1,6 +1,6 @@ import { FallbackRequestedError } from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction"; -import { WebauthnUtils } from "../webauthn-utils"; +import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; diff --git a/apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts similarity index 95% rename from apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts rename to apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index c235d53cb05..46ef4d60d69 100644 --- a/apps/browser/src/vault/fido2/content/page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -5,7 +5,7 @@ import { createCredentialRequestOptionsMock, setupMockedWebAuthnSupport, } from "../../../autofill/spec/fido2-testing-utils"; -import { WebauthnUtils } from "../webauthn-utils"; +import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; @@ -40,7 +40,7 @@ jest.mock("./messaging/messenger", () => { }, }; }); -jest.mock("../webauthn-utils"); +jest.mock("../../../vault/fido2/webauthn-utils"); describe("Fido2 page script with native WebAuthn support", () => { (jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation( @@ -53,7 +53,9 @@ describe("Fido2 page script with native WebAuthn support", () => { const mockCredentialAssertResult = createAssertCredentialResultMock(); setupMockedWebAuthnSupport(); - require("./page-script"); + beforeAll(() => { + require("./fido2-page-script"); + }); afterEach(() => { jest.resetModules(); @@ -152,7 +154,7 @@ describe("Fido2 page script with native WebAuthn support", () => { contentType: "json/application", })); - require("./content-script"); + require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); }); @@ -170,7 +172,7 @@ describe("Fido2 page script with native WebAuthn support", () => { }, })); - require("./content-script"); + require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); }); diff --git a/apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts similarity index 96% rename from apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts rename to apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index 4b1f839a1d0..a1e7006b045 100644 --- a/apps/browser/src/vault/fido2/content/page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -4,7 +4,7 @@ import { createCredentialCreationOptionsMock, createCredentialRequestOptionsMock, } from "../../../autofill/spec/fido2-testing-utils"; -import { WebauthnUtils } from "../webauthn-utils"; +import { WebauthnUtils } from "../../../vault/fido2/webauthn-utils"; import { MessageType } from "./messaging/message"; import { Messenger } from "./messaging/messenger"; @@ -39,7 +39,7 @@ jest.mock("./messaging/messenger", () => { }, }; }); -jest.mock("../webauthn-utils"); +jest.mock("../../../vault/fido2/webauthn-utils"); describe("Fido2 page script without native WebAuthn support", () => { (jest.spyOn(globalThis, "document", "get") as jest.Mock).mockImplementation( @@ -50,7 +50,7 @@ describe("Fido2 page script without native WebAuthn support", () => { const mockCreateCredentialsResult = createCreateCredentialResultMock(); const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); const mockCredentialAssertResult = createAssertCredentialResultMock(); - require("./page-script"); + require("./fido2-page-script"); afterEach(() => { jest.resetModules(); diff --git a/apps/browser/src/vault/fido2/content/messaging/message.ts b/apps/browser/src/autofill/fido2/content/messaging/message.ts similarity index 100% rename from apps/browser/src/vault/fido2/content/messaging/message.ts rename to apps/browser/src/autofill/fido2/content/messaging/message.ts diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts similarity index 100% rename from apps/browser/src/vault/fido2/content/messaging/messenger.spec.ts rename to apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts diff --git a/apps/browser/src/vault/fido2/content/messaging/messenger.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.ts similarity index 100% rename from apps/browser/src/vault/fido2/content/messaging/messenger.ts rename to apps/browser/src/autofill/fido2/content/messaging/messenger.ts diff --git a/apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts similarity index 55% rename from apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts rename to apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts index 287de6804bb..14e629b412e 100644 --- a/apps/browser/src/vault/fido2/enums/fido2-content-script.enum.ts +++ b/apps/browser/src/autofill/fido2/enums/fido2-content-script.enum.ts @@ -1,7 +1,7 @@ export const Fido2ContentScript = { - PageScript: "content/fido2/page-script.js", - PageScriptAppend: "content/fido2/page-script-append-mv2.js", - ContentScript: "content/fido2/content-script.js", + PageScript: "content/fido2-page-script.js", + PageScriptAppend: "content/fido2-page-script-append-mv2.js", + ContentScript: "content/fido2-content-script.js", } as const; export const Fido2ContentScriptId = { diff --git a/apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts b/apps/browser/src/autofill/fido2/enums/fido2-port-name.enum.ts similarity index 100% rename from apps/browser/src/vault/fido2/enums/fido2-port-name.enum.ts rename to apps/browser/src/autofill/fido2/enums/fido2-port-name.enum.ts diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index 56c67fba093..30e00d4e641 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -122,6 +122,54 @@
+

{{ "additionalOptions" | i18n }}

+
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
@@ -139,5 +187,35 @@ +
+
+ + +
+
+ +
+
+ + +
+
+
diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 1c6583331f4..7b8a1c32b44 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -4,13 +4,18 @@ 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 } from "@bitwarden/common/autofill/types"; +import { + InlineMenuVisibilitySetting, + ClearClipboardDelaySetting, +} from "@bitwarden/common/autofill/types"; import { UriMatchStrategy, UriMatchStrategySetting, } from "@bitwarden/common/models/domain/domain-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"; @@ -30,8 +35,14 @@ export class AutofillComponent implements OnInit { 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; @@ -42,6 +53,8 @@ export class AutofillComponent implements OnInit { private autofillService: AutofillService, private dialogService: DialogService, private autofillSettingsService: AutofillSettingsServiceAbstraction, + private messagingService: MessagingService, + private vaultSettingsService: VaultSettingsService, ) { this.autoFillOverlayVisibilityOptions = [ { @@ -61,6 +74,15 @@ export class AutofillComponent implements OnInit { { 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("baseDomain"), value: UriMatchStrategy.Domain }, { name: i18nService.t("host"), value: UriMatchStrategy.Host }, @@ -95,6 +117,14 @@ export class AutofillComponent implements OnInit { 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$, ); @@ -102,6 +132,12 @@ export class AutofillComponent implements OnInit { const command = await this.platformUtilsService.getAutofillKeyboardShortcut(); await this.setAutofillKeyboardHelperText(command); + + this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$); + + this.showIdentitiesCurrentTab = await firstValueFrom( + this.vaultSettingsService.showIdentitiesCurrentTab$, + ); } async updateAutoFillOverlayVisibility() { @@ -241,4 +277,25 @@ export class AutofillComponent implements OnInit { 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); + } } 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 4b786e6ca31..43c0817eaf2 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -186,9 +186,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte this.overlayButtonElement.remove(); this.isOverlayButtonVisible = 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.sendExtensionMessage("autofillOverlayElementClosed", { + void this.sendExtensionMessage("autofillOverlayElementClosed", { overlayElement: AutofillOverlayElement.Button, }); this.removeOverlayRepositionEventListeners(); @@ -204,9 +202,7 @@ class AutofillOverlayContentService implements AutofillOverlayContentServiceInte this.overlayListElement.remove(); this.isOverlayListVisible = 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.sendExtensionMessage("autofillOverlayElementClosed", { + void this.sendExtensionMessage("autofillOverlayElementClosed", { overlayElement: AutofillOverlayElement.List, }); } diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 24b1c90b3fa..23f690544df 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -1,6 +1,8 @@ import { mock, mockReset, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsService } from "@bitwarden/common/autofill/services/autofill-settings.service"; @@ -78,12 +80,17 @@ describe("AutofillService", () => { const userVerificationService = mock(); const billingAccountProfileStateService = mock(); const platformUtilsService = mock(); + let activeAccountStatusMock$: BehaviorSubject; + let authService: MockProxy; beforeEach(() => { scriptInjectorService = new BrowserScriptInjectorService(platformUtilsService, logService); inlineMenuVisibilityMock$ = new BehaviorSubject(AutofillOverlayVisibility.OnFieldFocus); autofillSettingsService = mock(); (autofillSettingsService as any).inlineMenuVisibility$ = inlineMenuVisibilityMock$; + activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Unlocked); + authService = mock(); + authService.activeAccountStatus$ = activeAccountStatusMock$; autofillService = new AutofillService( cipherService, autofillSettingsService, @@ -95,6 +102,7 @@ describe("AutofillService", () => { billingAccountProfileStateService, scriptInjectorService, accountService, + authService, ); domainSettingsService = new DefaultDomainSettingsService(fakeStateProvider); @@ -287,6 +295,18 @@ describe("AutofillService", () => { }); }); + it("skips injecting the autofiller script when the user's account is not unlocked", async () => { + activeAccountStatusMock$.next(AuthenticationStatus.Locked); + + await autofillService.injectAutofillScripts(sender.tab, sender.frameId, true); + + expect(BrowserApi.executeScriptInTab).not.toHaveBeenCalledWith(tabMock.id, { + file: "content/autofiller.js", + frameId: sender.frameId, + ...defaultExecuteScriptOptions, + }); + }); + it("will inject the bootstrap-autofill-overlay script if the user has the autofill overlay enabled", async () => { await autofillService.injectAutofillScripts(sender.tab, sender.frameId); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 5348ca5b9ab..9ec2052381a 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -3,7 +3,9 @@ import { pairwise } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; 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"; @@ -61,6 +63,7 @@ export default class AutofillService implements AutofillServiceInterface { private billingAccountProfileStateService: BillingAccountProfileStateService, private scriptInjectorService: ScriptInjectorService, private accountService: AccountService, + private authService: AuthService, ) {} /** @@ -113,6 +116,8 @@ export default class AutofillService implements AutofillServiceInterface { // Autofill user settings loaded from state can await the active account state indefinitely // if not guarded by an active account check (e.g. the user is logged in) const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + const authStatus = await firstValueFrom(this.authService.activeAccountStatus$); + const accountIsUnlocked = authStatus === AuthenticationStatus.Unlocked; let overlayVisibility: InlineMenuVisibilitySetting = AutofillOverlayVisibility.Off; let autoFillOnPageLoadIsEnabled = false; @@ -126,7 +131,7 @@ export default class AutofillService implements AutofillServiceInterface { const injectedScripts = [mainAutofillScript]; - if (activeAccount) { + if (activeAccount && accountIsUnlocked) { autoFillOnPageLoadIsEnabled = await this.getAutofillOnPageLoad(); } diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 9c957f6b1be..021b7719b2b 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -249,6 +249,7 @@ function createFocusedFieldDataMock(customFields = {}) { paddingRight: "6px", paddingLeft: "6px", }, + tabId: 1, ...customFields, }; } diff --git a/apps/browser/src/autofill/spec/testing-utils.ts b/apps/browser/src/autofill/spec/testing-utils.ts index 6d07e6606e0..5b0db5ebd6f 100644 --- a/apps/browser/src/autofill/spec/testing-utils.ts +++ b/apps/browser/src/autofill/spec/testing-utils.ts @@ -15,7 +15,7 @@ function postWindowMessage(data: any, origin = "https://localhost/", source = wi globalThis.dispatchEvent(new MessageEvent("message", { data, origin, source })); } -function sendExtensionRuntimeMessage( +function sendMockExtensionMessage( message: any, sender?: chrome.runtime.MessageSender, sendResponse?: CallableFunction, @@ -130,7 +130,7 @@ export { triggerTestFailure, flushPromises, postWindowMessage, - sendExtensionRuntimeMessage, + sendMockExtensionMessage, triggerRuntimeOnConnectEvent, sendPortMessage, triggerPortOnDisconnectEvent, diff --git a/apps/browser/src/autofill/utils/index.spec.ts b/apps/browser/src/autofill/utils/index.spec.ts index af67d416015..dcb5aa64696 100644 --- a/apps/browser/src/autofill/utils/index.spec.ts +++ b/apps/browser/src/autofill/utils/index.spec.ts @@ -37,14 +37,29 @@ describe("generateRandomCustomElementName", () => { }); describe("sendExtensionMessage", () => { - it("sends a message to the extention", () => { - const extensionMessageResponse = sendExtensionMessage("updateAutofillOverlayHidden", { + it("sends a message to the extension", async () => { + const extensionMessagePromise = sendExtensionMessage("updateAutofillOverlayHidden", { display: "none", }); - jest.spyOn(chrome.runtime, "sendMessage"); - expect(chrome.runtime.sendMessage).toHaveBeenCalled(); - expect(extensionMessageResponse).toEqual(Promise.resolve({})); + // Jest doesn't give anyway to select the typed overload of "sendMessage", + // a cast is needed to get the correct spy type. + const sendMessageSpy = jest.spyOn(chrome.runtime, "sendMessage") as unknown as jest.SpyInstance< + void, + [message: string, responseCallback: (response: string) => void], + unknown + >; + + expect(sendMessageSpy).toHaveBeenCalled(); + + const [latestCall] = sendMessageSpy.mock.calls; + const responseCallback = latestCall[1]; + + responseCallback("sendMessageResponse"); + + const response = await extensionMessagePromise; + + expect(response).toEqual("sendMessageResponse"); }); }); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index a382a76781a..9af1a14132f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -9,6 +9,7 @@ import { AuthRequestService, LoginEmailServiceAbstraction, LoginEmailService, + LogoutReason, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -135,6 +136,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for service creation +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { DefaultThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ApiService } from "@bitwarden/common/services/api.service"; import { AuditService } from "@bitwarden/common/services/audit.service"; @@ -165,8 +169,6 @@ import { CollectionService as CollectionServiceAbstraction } from "@bitwarden/co import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService as InternalFolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -175,8 +177,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; import { @@ -202,6 +202,8 @@ import WebRequestBackground from "../autofill/background/web-request.background" import { CipherContextMenuHandler } from "../autofill/browser/cipher-context-menu-handler"; import { ContextMenuClickedHandler } from "../autofill/browser/context-menu-clicked-handler"; import { MainContextMenuHandler } from "../autofill/browser/main-context-menu-handler"; +import { Fido2Background as Fido2BackgroundAbstraction } from "../autofill/fido2/background/abstractions/fido2.background"; +import { Fido2Background } from "../autofill/fido2/background/fido2.background"; import { AutofillService as AutofillServiceAbstraction } from "../autofill/services/abstractions/autofill.service"; import AutofillService from "../autofill/services/autofill.service"; import { SafariApp } from "../browser/safariApp"; @@ -233,8 +235,6 @@ import { SyncServiceListener } from "../platform/sync/sync-service.listener"; import { fromChromeRuntimeMessaging } from "../platform/utils/from-chrome-runtime-messaging"; import VaultTimeoutService from "../services/vault-timeout/vault-timeout.service"; import FilelessImporterBackground from "../tools/background/fileless-importer.background"; -import { Fido2Background as Fido2BackgroundAbstraction } from "../vault/fido2/background/abstractions/fido2.background"; -import { Fido2Background } from "../vault/fido2/background/fido2.background"; import { BrowserFido2UserInterfaceService } from "../vault/fido2/browser-fido2-user-interface.service"; import { VaultFilterService } from "../vault/services/vault-filter.service"; @@ -267,7 +267,7 @@ export default class MainBackground { collectionService: CollectionServiceAbstraction; vaultTimeoutService: VaultTimeoutService; vaultTimeoutSettingsService: VaultTimeoutSettingsServiceAbstraction; - syncService: SyncServiceAbstraction; + syncService: SyncService; passwordGenerationService: PasswordGenerationServiceAbstraction; passwordStrengthService: PasswordStrengthServiceAbstraction; totpService: TotpServiceAbstraction; @@ -305,7 +305,6 @@ export default class MainBackground { policyApiService: PolicyApiServiceAbstraction; sendApiService: SendApiServiceAbstraction; userVerificationApiService: UserVerificationApiServiceAbstraction; - syncNotifierService: SyncNotifierServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; fido2ClientService: Fido2ClientServiceAbstraction; @@ -334,7 +333,7 @@ export default class MainBackground { ssoLoginService: SsoLoginServiceAbstraction; billingAccountProfileStateService: BillingAccountProfileStateService; // eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module - intraprocessMessagingSubject: Subject>; + intraprocessMessagingSubject: Subject>>; userAutoUnlockKeyService: UserAutoUnlockKeyService; scriptInjectorService: BrowserScriptInjectorService; kdfConfigService: kdfConfigServiceAbstraction; @@ -375,8 +374,17 @@ export default class MainBackground { } }; - const logoutCallback = async (expired: boolean, userId?: UserId) => - await this.logout(expired, userId); + const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) => + await this.logout(logoutReason, userId); + + const refreshAccessTokenErrorCallback = () => { + // Send toast to popup + this.messagingService.send("showToast", { + type: "error", + title: this.i18nService.t("errorRefreshingAccessToken"), + message: this.i18nService.t("errorRefreshingAccessTokenDesc"), + }); + }; const isDev = process.env.ENV === "development"; this.logService = new ConsoleLogService(isDev); @@ -384,7 +392,7 @@ export default class MainBackground { this.keyGenerationService = new KeyGenerationService(this.cryptoFunctionService); this.storageService = new BrowserLocalStorageService(); - this.intraprocessMessagingSubject = new Subject>(); + this.intraprocessMessagingSubject = new Subject>>(); this.messagingService = MessageSender.combine( new SubjectMessageSender(this.intraprocessMessagingSubject), @@ -523,6 +531,7 @@ export default class MainBackground { this.keyGenerationService, this.encryptService, this.logService, + logoutCallback, ); const migrationRunner = new MigrationRunner( @@ -608,9 +617,12 @@ export default class MainBackground { this.platformUtilsService, this.environmentService, this.appIdService, + refreshAccessTokenErrorCallback, + this.logService, + (logoutReason: LogoutReason, userId?: UserId) => this.logout(logoutReason, userId), this.vaultTimeoutSettingsService, - (expired: boolean) => this.logout(expired), ); + this.domainSettingsService = new DefaultDomainSettingsService(this.stateProvider); this.fileUploadService = new FileUploadService(this.logService); this.cipherFileUploadService = new CipherFileUploadService( @@ -624,7 +636,6 @@ export default class MainBackground { this.i18nService, this.stateProvider, ); - this.syncNotifierService = new SyncNotifierService(); this.autofillSettingsService = new AutofillSettingsService( this.stateProvider, @@ -813,7 +824,7 @@ export default class MainBackground { messageListener, ); } else { - this.syncService = new SyncService( + this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, this.apiService, @@ -840,7 +851,12 @@ export default class MainBackground { this.authService, ); - this.syncServiceListener = new SyncServiceListener(this.syncService, messageListener); + this.syncServiceListener = new SyncServiceListener( + this.syncService, + messageListener, + this.messagingService, + this.logService, + ); } this.eventUploadService = new EventUploadService( this.apiService, @@ -872,6 +888,7 @@ export default class MainBackground { this.billingAccountProfileStateService, this.scriptInjectorService, this.accountService, + this.authService, ); this.auditService = new AuditService(this.cryptoFunctionService, this.apiService); @@ -1169,7 +1186,7 @@ export default class MainBackground { this.contextMenusBackground?.init(); await this.idleBackground.init(); this.webRequestBackground?.startListening(); - this.syncServiceListener?.startListening(); + this.syncServiceListener?.listener$().subscribe(); return new Promise((resolve) => { setTimeout(async () => { @@ -1277,7 +1294,7 @@ export default class MainBackground { } } - async logout(expired: boolean, userId?: UserId) { + async logout(logoutReason: LogoutReason, userId?: UserId) { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe( map((a) => a?.id), @@ -1332,7 +1349,7 @@ export default class MainBackground { ]); //Needs to be checked before state is cleaned - const needStorageReseed = await this.needsStorageReseed(userId); + const needStorageReseed = await this.needsStorageReseed(userBeingLoggedOut); await this.stateService.clean({ userId: userBeingLoggedOut }); await this.accountService.clean(userBeingLoggedOut); @@ -1343,7 +1360,7 @@ export default class MainBackground { await logoutPromise; this.messagingService.send("doneLoggingOut", { - expired: expired, + logoutReason: logoutReason, userId: userBeingLoggedOut, }); diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 7ddd5302ac7..a1a5de54d21 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -20,11 +20,11 @@ import { openTwoFactorAuthPopout, } from "../auth/popup/utils/auth-popout-window"; import { LockedVaultPendingNotificationsData } from "../autofill/background/abstractions/notification.background"; +import { Fido2Background } from "../autofill/fido2/background/abstractions/fido2.background"; import { AutofillService } from "../autofill/services/abstractions/autofill.service"; import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserEnvironmentService } from "../platform/services/browser-environment.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; -import { Fido2Background } from "../vault/fido2/background/abstractions/fido2.background"; import MainBackground from "./main.background"; diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index fc0ff51230d..7f6e4957158 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.5.0", + "version": "2024.6.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -101,7 +101,7 @@ } }, "web_accessible_resources": [ - "content/fido2/page-script.js", + "content/fido2-page-script.js", "content/lp-suppress-import-download.js", "notification/bar.html", "images/icon38.png", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 0720b65a913..26b63b1229f 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.5.0", + "version": "2024.6.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", @@ -107,7 +107,7 @@ "web_accessible_resources": [ { "resources": [ - "content/fido2/page-script.js", + "content/fido2-page-script.js", "notification/bar.html", "images/icon38.png", "images/icon38_locked.png", diff --git a/apps/browser/src/platform/messaging/chrome-message.sender.ts b/apps/browser/src/platform/messaging/chrome-message.sender.ts index 0e57ecfb4ec..914b8fd43a4 100644 --- a/apps/browser/src/platform/messaging/chrome-message.sender.ts +++ b/apps/browser/src/platform/messaging/chrome-message.sender.ts @@ -15,9 +15,9 @@ const HANDLED_ERRORS: Record = { export class ChromeMessageSender implements MessageSender { constructor(private readonly logService: LogService) {} - send( + send>( commandDefinition: string | CommandDefinition, - payload: object | T = {}, + payload: Record | T = {}, ): void { const command = getCommand(commandDefinition); chrome.runtime.sendMessage(Object.assign(payload, { command: command }), () => { 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 933cd08c2ea..4065f2e46d7 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -1,4 +1,4 @@ -import { flushPromises, sendExtensionRuntimeMessage } from "../../autofill/spec/testing-utils"; +import { flushPromises, sendMockExtensionMessage } from "../../autofill/spec/testing-utils"; import { BrowserApi } from "../browser/browser-api"; import BrowserClipboardService from "../services/browser-clipboard.service"; @@ -21,7 +21,7 @@ describe("OffscreenDocument", () => { describe("extension message handlers", () => { it("ignores messages that do not have a handler registered with the corresponding command", () => { - sendExtensionRuntimeMessage({ command: "notAValidCommand" }); + sendMockExtensionMessage({ command: "notAValidCommand" }); expect(browserClipboardServiceCopySpy).not.toHaveBeenCalled(); expect(browserClipboardServiceReadSpy).not.toHaveBeenCalled(); @@ -31,7 +31,7 @@ describe("OffscreenDocument", () => { const error = new Error("test error"); browserClipboardServiceCopySpy.mockRejectedValueOnce(new Error("test error")); - sendExtensionRuntimeMessage({ command: "offscreenCopyToClipboard", text: "test" }); + sendMockExtensionMessage({ command: "offscreenCopyToClipboard", text: "test" }); await flushPromises(); expect(browserClipboardServiceCopySpy).toHaveBeenCalled(); @@ -45,7 +45,7 @@ describe("OffscreenDocument", () => { it("copies the message text", async () => { const text = "test"; - sendExtensionRuntimeMessage({ command: "offscreenCopyToClipboard", text }); + sendMockExtensionMessage({ command: "offscreenCopyToClipboard", text }); await flushPromises(); expect(browserClipboardServiceCopySpy).toHaveBeenCalledWith(window, text); @@ -54,7 +54,7 @@ describe("OffscreenDocument", () => { describe("handleOffscreenReadFromClipboard", () => { it("reads the value from the clipboard service", async () => { - sendExtensionRuntimeMessage({ command: "offscreenReadFromClipboard" }); + sendMockExtensionMessage({ command: "offscreenReadFromClipboard" }); await flushPromises(); expect(browserClipboardServiceReadSpy).toHaveBeenCalledWith(window); 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 ece6be4c63e..b3dcd626ae3 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -1,6 +1,6 @@ -
-
+
+
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html deleted file mode 100644 index 4fdbb823120..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-

- {{ title }} -

- -
-
- -
-
diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts deleted file mode 100644 index b33a2a0f330..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Input } from "@angular/core"; - -import { TypographyModule } from "@bitwarden/components"; - -@Component({ - standalone: true, - selector: "popup-section-header", - templateUrl: "./popup-section-header.component.html", - imports: [TypographyModule], -}) -export class PopupSectionHeaderComponent { - @Input() title: string; -} diff --git a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts b/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts deleted file mode 100644 index f5cb472a59c..00000000000 --- a/apps/browser/src/platform/popup/popup-section-header/popup-section-header.stories.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; - -import { - CardComponent, - IconButtonModule, - SectionComponent, - TypographyModule, -} from "@bitwarden/components"; - -import { PopupSectionHeaderComponent } from "./popup-section-header.component"; - -export default { - title: "Browser/Popup Section Header", - component: PopupSectionHeaderComponent, - args: { - title: "Title", - }, - decorators: [ - moduleMetadata({ - imports: [SectionComponent, CardComponent, TypographyModule, IconButtonModule], - }), - ], -} as Meta; - -type Story = StoryObj; - -export const OnlyTitle: Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), - args: { - title: "Only Title", - }, -}; - -export const TrailingText: Story = { - render: (args) => ({ - props: args, - template: ` - - 13 - - `, - }), - args: { - title: "Trailing Text", - }, -}; - -export const TailingIcon: Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), - args: { - title: "Trailing Icon", - }, -}; - -export const TitleSuffix: Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), - args: { - title: "Title Suffix", - }, -}; - -export const WithSections: Story = { - render: () => ({ - template: ` -
- - - - - -

Card 1 Content

-
-
- - - - - -

Card 2 Content

-
-
-
- `, - }), -}; diff --git a/apps/browser/src/platform/sync/foreground-sync.service.spec.ts b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts new file mode 100644 index 00000000000..a9ee7c23b9c --- /dev/null +++ b/apps/browser/src/platform/sync/foreground-sync.service.spec.ts @@ -0,0 +1,130 @@ +import { mock } from "jest-mock-extended"; +import { Subject } from "rxjs"; + +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"; +import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; +import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; +import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; +import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; + +import { DO_FULL_SYNC, ForegroundSyncService, FullSyncMessage } from "./foreground-sync.service"; +import { FullSyncFinishedMessage } from "./sync-service.listener"; + +describe("ForegroundSyncService", () => { + const stateService = mock(); + const folderService = mock(); + const folderApiService = mock(); + const messageSender = mock(); + const logService = mock(); + const cipherService = mock(); + const collectionService = mock(); + const apiService = mock(); + const accountService = mock(); + const authService = mock(); + const sendService = mock(); + const sendApiService = mock(); + const messageListener = mock(); + + const sut = new ForegroundSyncService( + stateService, + folderService, + folderApiService, + messageSender, + logService, + cipherService, + collectionService, + apiService, + accountService, + authService, + sendService, + sendApiService, + messageListener, + ); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe("fullSync", () => { + const getAndAssertRequestId = (doFullSyncMessage: Omit) => { + expect(messageSender.send).toHaveBeenCalledWith( + DO_FULL_SYNC, + // We don't know the request id since that is created internally + expect.objectContaining(doFullSyncMessage), + ); + + const message = messageSender.send.mock.calls[0][1]; + + if (!("requestId" in message) || typeof message.requestId !== "string") { + throw new Error("requestId property of type string was expected on the sent message."); + } + + return message.requestId; + }; + + it("correctly relays a successful fullSync", async () => { + const messages = new Subject(); + messageListener.messages$.mockReturnValue(messages); + const fullSyncPromise = sut.fullSync(true, false); + expect(sut.syncInProgress).toBe(true); + + const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: false }); + + // Pretend the sync has finished + messages.next({ successfully: true, errorMessage: null, requestId: requestId }); + + const result = await fullSyncPromise; + + expect(sut.syncInProgress).toBe(false); + expect(result).toBe(true); + }); + + it("correctly relays an unsuccessful fullSync but does not throw if allowThrowOnError = false", async () => { + const messages = new Subject(); + messageListener.messages$.mockReturnValue(messages); + const fullSyncPromise = sut.fullSync(false, false); + expect(sut.syncInProgress).toBe(true); + + const requestId = getAndAssertRequestId({ forceSync: false, allowThrowOnError: false }); + + // Pretend the sync has finished + messages.next({ + successfully: false, + errorMessage: "Error while syncing", + requestId: requestId, + }); + + const result = await fullSyncPromise; + + expect(sut.syncInProgress).toBe(false); + expect(result).toBe(false); + }); + + it("correctly relays an unsuccessful fullSync but and will throw if allowThrowOnError = true", async () => { + const messages = new Subject(); + messageListener.messages$.mockReturnValue(messages); + const fullSyncPromise = sut.fullSync(true, true); + expect(sut.syncInProgress).toBe(true); + + const requestId = getAndAssertRequestId({ forceSync: true, allowThrowOnError: true }); + + // Pretend the sync has finished + messages.next({ + successfully: false, + errorMessage: "Error while syncing", + requestId: requestId, + }); + + await expect(fullSyncPromise).rejects.toThrow("Error while syncing"); + + expect(sut.syncInProgress).toBe(false); + }); + }); +}); diff --git a/apps/browser/src/platform/sync/foreground-sync.service.ts b/apps/browser/src/platform/sync/foreground-sync.service.ts index 3c144316724..0a2c7074298 100644 --- a/apps/browser/src/platform/sync/foreground-sync.service.ts +++ b/apps/browser/src/platform/sync/foreground-sync.service.ts @@ -1,4 +1,4 @@ -import { firstValueFrom, timeout } from "rxjs"; +import { filter, firstValueFrom, of, timeout } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -10,6 +10,7 @@ import { MessageListener, MessageSender, } from "@bitwarden/common/platform/messaging"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CoreSyncService } from "@bitwarden/common/platform/sync/internal"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { InternalSendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; @@ -18,11 +19,11 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -const SYNC_COMPLETED = new CommandDefinition<{ successfully: boolean }>("syncCompleted"); -export const DO_FULL_SYNC = new CommandDefinition<{ - forceSync: boolean; - allowThrowOnError: boolean; -}>("doFullSync"); +import { FULL_SYNC_FINISHED } from "./sync-service.listener"; + +export type FullSyncMessage = { forceSync: boolean; allowThrowOnError: boolean; requestId: string }; + +export const DO_FULL_SYNC = new CommandDefinition("doFullSync"); export class ForegroundSyncService extends CoreSyncService { constructor( @@ -59,18 +60,29 @@ export class ForegroundSyncService extends CoreSyncService { async fullSync(forceSync: boolean, allowThrowOnError: boolean = false): Promise { this.syncInProgress = true; try { + const requestId = Utils.newGuid(); const syncCompletedPromise = firstValueFrom( - this.messageListener.messages$(SYNC_COMPLETED).pipe( + this.messageListener.messages$(FULL_SYNC_FINISHED).pipe( + filter((m) => m.requestId === requestId), timeout({ - first: 10_000, + first: 30_000, + // If we haven't heard back in 30 seconds, just pretend we heard back about an unsuccesful sync. with: () => { - throw new Error("Timeout while doing a fullSync call."); + this.logService.warning( + "ForegroundSyncService did not receive a message back in a reasonable time.", + ); + return of({ successfully: false, errorMessage: "Sync timed out." }); }, }), ), ); - this.messageSender.send(DO_FULL_SYNC, { forceSync, allowThrowOnError }); + this.messageSender.send(DO_FULL_SYNC, { forceSync, allowThrowOnError, requestId }); const result = await syncCompletedPromise; + + if (allowThrowOnError && result.errorMessage != null) { + throw new Error(result.errorMessage); + } + return result.successfully; } finally { this.syncInProgress = false; diff --git a/apps/browser/src/platform/sync/sync-service.listener.spec.ts b/apps/browser/src/platform/sync/sync-service.listener.spec.ts new file mode 100644 index 00000000000..51f97e9f879 --- /dev/null +++ b/apps/browser/src/platform/sync/sync-service.listener.spec.ts @@ -0,0 +1,60 @@ +import { mock } from "jest-mock-extended"; +import { Subject, firstValueFrom } from "rxjs"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; +import { tagAsExternal } from "@bitwarden/common/platform/messaging/helpers"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +import { FullSyncMessage } from "./foreground-sync.service"; +import { FULL_SYNC_FINISHED, SyncServiceListener } from "./sync-service.listener"; + +describe("SyncServiceListener", () => { + const syncService = mock(); + const messageListener = mock(); + const messageSender = mock(); + const logService = mock(); + + const messages = new Subject(); + messageListener.messages$.mockReturnValue(messages.asObservable().pipe(tagAsExternal())); + const sut = new SyncServiceListener(syncService, messageListener, messageSender, logService); + + describe("listener$", () => { + it.each([true, false])( + "calls full sync and relays outcome when sync is [successfully = %s]", + async (value) => { + const listener = sut.listener$(); + const emissionPromise = firstValueFrom(listener); + + syncService.fullSync.mockResolvedValueOnce(value); + messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" }); + + await emissionPromise; + + expect(syncService.fullSync).toHaveBeenCalledWith(true, false); + expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, { + successfully: value, + errorMessage: null, + requestId: "1", + }); + }, + ); + + it("calls full sync and relays error message through messaging", async () => { + const listener = sut.listener$(); + const emissionPromise = firstValueFrom(listener); + + syncService.fullSync.mockRejectedValueOnce(new Error("SyncError")); + messages.next({ forceSync: true, allowThrowOnError: false, requestId: "1" }); + + await emissionPromise; + + expect(syncService.fullSync).toHaveBeenCalledWith(true, false); + expect(messageSender.send).toHaveBeenCalledWith(FULL_SYNC_FINISHED, { + successfully: false, + errorMessage: "SyncError", + requestId: "1", + }); + }); + }); +}); diff --git a/apps/browser/src/platform/sync/sync-service.listener.ts b/apps/browser/src/platform/sync/sync-service.listener.ts index b9e18accacd..079edbf4c71 100644 --- a/apps/browser/src/platform/sync/sync-service.listener.ts +++ b/apps/browser/src/platform/sync/sync-service.listener.ts @@ -1,25 +1,58 @@ -import { Subscription, concatMap, filter } from "rxjs"; +import { Observable, concatMap, filter } from "rxjs"; -import { MessageListener, isExternalMessage } from "@bitwarden/common/platform/messaging"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { + CommandDefinition, + MessageListener, + MessageSender, + isExternalMessage, +} from "@bitwarden/common/platform/messaging"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DO_FULL_SYNC } from "./foreground-sync.service"; +export type FullSyncFinishedMessage = { + successfully: boolean; + errorMessage: string; + requestId: string; +}; + +export const FULL_SYNC_FINISHED = new CommandDefinition( + "fullSyncFinished", +); + export class SyncServiceListener { constructor( private readonly syncService: SyncService, private readonly messageListener: MessageListener, + private readonly messageSender: MessageSender, + private readonly logService: LogService, ) {} - startListening(): Subscription { - return this.messageListener - .messages$(DO_FULL_SYNC) - .pipe( - filter((message) => isExternalMessage(message)), - concatMap(async ({ forceSync, allowThrowOnError }) => { - await this.syncService.fullSync(forceSync, allowThrowOnError); - }), - ) - .subscribe(); + listener$(): Observable { + return this.messageListener.messages$(DO_FULL_SYNC).pipe( + filter((message) => isExternalMessage(message)), + concatMap(async ({ forceSync, allowThrowOnError, requestId }) => { + await this.doFullSync(forceSync, allowThrowOnError, requestId); + }), + ); + } + + private async doFullSync(forceSync: boolean, allowThrowOnError: boolean, requestId: string) { + try { + const result = await this.syncService.fullSync(forceSync, allowThrowOnError); + this.messageSender.send(FULL_SYNC_FINISHED, { + successfully: result, + errorMessage: null, + requestId, + }); + } catch (err) { + this.logService.warning("Error while doing full sync in SyncServiceListener", err); + this.messageSender.send(FULL_SYNC_FINISHED, { + successfully: false, + errorMessage: err?.message ?? "Unknown Sync Error", + requestId, + }); + } } } diff --git a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts index e30f35b680b..ebc01ad86fa 100644 --- a/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts +++ b/apps/browser/src/platform/utils/from-chrome-runtime-messaging.ts @@ -1,5 +1,6 @@ import { map, share } from "rxjs"; +import { Message } from "@bitwarden/common/platform/messaging"; import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; import { fromChromeEvent } from "../browser/from-chrome-event"; @@ -20,7 +21,7 @@ export const fromChromeRuntimeMessaging = () => { return message; }), - tagAsExternal, + tagAsExternal>>(), share(), ); }; diff --git a/apps/browser/src/popup/app-routing.animations.ts b/apps/browser/src/popup/app-routing.animations.ts index b2fa53caba8..065331bd414 100644 --- a/apps/browser/src/popup/app-routing.animations.ts +++ b/apps/browser/src/popup/app-routing.animations.ts @@ -196,9 +196,6 @@ export const routerTransition = trigger("routerTransition", [ transition("vault-settings => sync", inSlideLeft), transition("sync => vault-settings", outSlideRight), - transition("tabs => options", inSlideLeft), - transition("options => tabs", outSlideRight), - // Appearance settings transition("tabs => appearance", inSlideLeft), transition("appearance => tabs", outSlideRight), diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 37504568336..97008ab96d9 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -40,7 +40,9 @@ import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-p 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 { ExportComponent } from "../tools/popup/settings/export.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"; @@ -64,7 +66,6 @@ import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.c import { extensionRefreshRedirect, extensionRefreshSwap } from "./extension-refresh-route-utils"; import { debounceNavigationGuard } from "./services/debounce-navigation.service"; -import { OptionsComponent } from "./settings/options.component"; import { TabsV2Component } from "./tabs-v2.component"; import { TabsComponent } from "./tabs.component"; @@ -238,18 +239,16 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "generator-history" }, }, - { + ...extensionRefreshSwap(ImportBrowserComponent, ImportBrowserV2Component, { path: "import", - component: ImportBrowserComponent, canActivate: [AuthGuard], data: { state: "import" }, - }, - { + }), + ...extensionRefreshSwap(ExportBrowserComponent, ExportBrowserV2Component, { path: "export", - component: ExportComponent, canActivate: [AuthGuard], data: { state: "export" }, - }, + }), { path: "autofill", component: AutofillComponent, @@ -309,12 +308,6 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { state: "premium" }, }, - { - path: "options", - component: OptionsComponent, - canActivate: [AuthGuard], - data: { state: "options" }, - }, { path: "appearance", component: AppearanceComponent, diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 7e94e84ef5d..b70a5564ed9 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angula import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs"; +import { LogoutReason } from "@bitwarden/auth/common"; 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"; @@ -10,7 +11,12 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { MessageListener } from "@bitwarden/common/platform/messaging"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { DialogService, SimpleDialogOptions, ToastService } from "@bitwarden/components"; +import { + DialogService, + SimpleDialogOptions, + ToastOptions, + ToastService, +} from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; import { BrowserStateService } from "../platform/services/abstractions/browser-state.service"; @@ -83,13 +89,10 @@ export class AppComponent implements OnInit, OnDestroy { .pipe( tap((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 () => { - if (msg.expired) { - this.toastService.showToast({ - variant: "warning", - title: this.i18nService.t("loggedOut"), - message: this.i18nService.t("loginExpired"), - }); + if (msg.logoutReason) { + await this.displayLogoutReason(msg.logoutReason); } }); this.changeDetectorRef.detectChanges(); @@ -233,4 +236,23 @@ export class AppComponent implements OnInit, OnDestroy { this.browserSendStateService.setBrowserSendTypeComponentState(null), ]); } + + // Displaying toasts isn't super useful on the popup due to the reloads we do. + // However, it is visible for a moment on the FF sidebar logout. + private async displayLogoutReason(logoutReason: LogoutReason) { + let toastOptions: ToastOptions; + switch (logoutReason) { + case "invalidSecurityStamp": + case "sessionExpired": { + toastOptions = { + variant: "warning", + title: this.i18nService.t("loggedOut"), + message: this.i18nService.t("loginExpired"), + }; + break; + } + } + + this.toastService.showToast(toastOptions); + } } diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 54a9da028f9..3c7f45e55f7 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -16,7 +16,6 @@ import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password- import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; import { AvatarModule, ButtonModule, ToastModule } from "@bitwarden/components"; -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AccountComponent } from "../auth/popup/account-switching/account.component"; @@ -47,7 +46,6 @@ import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.comp import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; -import { PopupSectionHeaderComponent } from "../platform/popup/popup-section-header/popup-section-header.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"; @@ -55,7 +53,6 @@ import { SendListComponent } from "../tools/popup/send/components/send-list.comp 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 { ExportComponent } from "../tools/popup/settings/export.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"; @@ -84,7 +81,6 @@ 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 { OptionsComponent } from "./settings/options.component"; import { TabsV2Component } from "./tabs-v2.component"; import { TabsComponent } from "./tabs.component"; @@ -116,14 +112,12 @@ import "../platform/popup/locales"; AvatarModule, AccountComponent, ButtonModule, - ExportScopeCalloutComponent, PopOutComponent, PopupPageComponent, PopupTabNavigationComponent, PopupFooterComponent, PopupHeaderComponent, UserVerificationDialogComponent, - PopupSectionHeaderComponent, CurrentAccountComponent, ], declarations: [ @@ -140,7 +134,6 @@ import "../platform/popup/locales"; CurrentTabComponent, EnvironmentComponent, ExcludedDomainsComponent, - ExportComponent, Fido2CipherRowComponent, Fido2UseBrowserLinkComponent, FolderAddEditComponent, @@ -153,7 +146,6 @@ import "../platform/popup/locales"; LoginComponent, LoginViaAuthRequestComponent, LoginDecryptionOptionsComponent, - OptionsComponent, NotificationsSettingsComponent, AppearanceComponent, GeneratorComponent, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index a4f5c8a4c67..d61fa3b19c0 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -25,7 +25,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeou 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 as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; @@ -34,7 +34,6 @@ import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -81,11 +80,11 @@ import { } from "@bitwarden/common/platform/state"; // eslint-disable-next-line import/no-restricted-paths -- Used for dependency injection import { InlineDerivedStateProvider } from "@bitwarden/common/platform/state/implementations/inline-derived-state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.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"; @@ -174,12 +173,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BaseUnauthGuardService, useClass: UnauthGuardService, - deps: [AuthServiceAbstraction, Router], - }), - safeProvider({ - provide: AuthServiceAbstraction, - useFactory: getBgService("authService"), - deps: [], + deps: [AuthService, Router], }), safeProvider({ provide: SsoLoginServiceAbstraction, @@ -347,6 +341,7 @@ const safeProviders: SafeProvider[] = [ BillingAccountProfileStateService, ScriptInjectorService, AccountServiceAbstraction, + AuthService, ], }), safeProvider({ @@ -529,7 +524,7 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: MessageListener, - useFactory: (subject: Subject>, ngZone: NgZone) => + useFactory: (subject: Subject>>, ngZone: NgZone) => new MessageListener( merge( subject.asObservable(), // For messages in the same context @@ -540,7 +535,7 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: MessageSender, - useFactory: (subject: Subject>, logService: LogService) => + useFactory: (subject: Subject>>, logService: LogService) => MessageSender.combine( new SubjectMessageSender(subject), // For sending messages in the same context new ChromeMessageSender(logService), // For sending messages to different contexts @@ -555,14 +550,14 @@ const safeProviders: SafeProvider[] = [ // we need the same instance that our in memory background is utilizing. return getBgService("intraprocessMessagingSubject")(); } else { - return new Subject>(); + return new Subject>>(); } }, deps: [], }), safeProvider({ provide: MessageSender, - useFactory: (subject: Subject>, logService: LogService) => + useFactory: (subject: Subject>>, logService: LogService) => MessageSender.combine( new SubjectMessageSender(subject), // For sending messages in the same context new ChromeMessageSender(logService), // For sending messages to different contexts @@ -581,7 +576,7 @@ const safeProviders: SafeProvider[] = [ // There isn't a locally created background so we will communicate with // the true background through chrome apis, in that case, we can just create // one for ourself. - return new Subject>(); + return new Subject>>(); } }, deps: [], diff --git a/apps/browser/src/popup/settings/options.component.html b/apps/browser/src/popup/settings/options.component.html deleted file mode 100644 index 0382eb5b866..00000000000 --- a/apps/browser/src/popup/settings/options.component.html +++ /dev/null @@ -1,135 +0,0 @@ -
-
- -
-

- {{ "options" | i18n }} -

-
-
-
-
-

- -

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

- -

-
- -
-
-
- - -
-
- -
-
-
-
- - -
-
- -
-
-
diff --git a/apps/browser/src/popup/settings/options.component.ts b/apps/browser/src/popup/settings/options.component.ts deleted file mode 100644 index cfcc81bb22c..00000000000 --- a/apps/browser/src/popup/settings/options.component.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { firstValueFrom } from "rxjs"; - -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; -import { ClearClipboardDelaySetting } from "@bitwarden/common/autofill/types"; -import { - UriMatchStrategy, - UriMatchStrategySetting, -} from "@bitwarden/common/models/domain/domain-service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; - -import { enableAccountSwitching } from "../../platform/flags"; - -@Component({ - selector: "app-options", - templateUrl: "options.component.html", -}) -export class OptionsComponent implements OnInit { - enableAutoFillOnPageLoad = false; - autoFillOnPageLoadDefault = false; - autoFillOnPageLoadOptions: any[]; - enableAutoTotpCopy = false; // TODO: Does it matter if this is set to false or true? - enableContextMenuItem = false; - showCardsCurrentTab = false; - showIdentitiesCurrentTab = false; - showClearClipboard = true; - defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; - uriMatchOptions: any[]; - clearClipboard: ClearClipboardDelaySetting; - clearClipboardOptions: any[]; - showGeneral = true; - showDisplay = true; - accountSwitcherEnabled = false; - - constructor( - private messagingService: MessagingService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private domainSettingsService: DomainSettingsService, - i18nService: I18nService, - private vaultSettingsService: VaultSettingsService, - ) { - this.uriMatchOptions = [ - { name: i18nService.t("baseDomain"), 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.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.autoFillOnPageLoadOptions = [ - { name: i18nService.t("autoFillOnPageLoadYes"), value: true }, - { name: i18nService.t("autoFillOnPageLoadNo"), value: false }, - ]; - - this.accountSwitcherEnabled = enableAccountSwitching(); - } - - async ngOnInit() { - this.enableAutoFillOnPageLoad = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoad$, - ); - - this.autoFillOnPageLoadDefault = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadDefault$, - ); - - this.enableContextMenuItem = await firstValueFrom( - this.autofillSettingsService.enableContextMenu$, - ); - - this.showCardsCurrentTab = await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$); - this.showIdentitiesCurrentTab = await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - ); - - this.enableAutoTotpCopy = await firstValueFrom(this.autofillSettingsService.autoCopyTotp$); - - const defaultUriMatch = await firstValueFrom( - this.domainSettingsService.defaultUriMatchStrategy$, - ); - this.defaultUriMatch = defaultUriMatch == null ? UriMatchStrategy.Domain : defaultUriMatch; - - this.clearClipboard = await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$); - } - - async updateContextMenuItem() { - await this.autofillSettingsService.setEnableContextMenu(this.enableContextMenuItem); - this.messagingService.send("bgUpdateContextMenu"); - } - - async updateAutoTotpCopy() { - await this.autofillSettingsService.setAutoCopyTotp(this.enableAutoTotpCopy); - } - - async updateAutoFillOnPageLoad() { - await this.autofillSettingsService.setAutofillOnPageLoad(this.enableAutoFillOnPageLoad); - } - - async updateAutoFillOnPageLoadDefault() { - await this.autofillSettingsService.setAutofillOnPageLoadDefault(this.autoFillOnPageLoadDefault); - } - - async updateShowCardsCurrentTab() { - await this.vaultSettingsService.setShowCardsCurrentTab(this.showCardsCurrentTab); - } - - async updateShowIdentitiesCurrentTab() { - await this.vaultSettingsService.setShowIdentitiesCurrentTab(this.showIdentitiesCurrentTab); - } - - async saveClearClipboard() { - await this.autofillSettingsService.setClearClipboardDelay(this.clearClipboard); - } -} diff --git a/apps/browser/src/tools/popup/services/key-definitions.ts b/apps/browser/src/tools/popup/services/key-definitions.ts index 9b256073f3f..b4ccd991e7b 100644 --- a/apps/browser/src/tools/popup/services/key-definitions.ts +++ b/apps/browser/src/tools/popup/services/key-definitions.ts @@ -1,23 +1,25 @@ import { Jsonify } from "type-fest"; -import { BROWSER_SEND_MEMORY, KeyDefinition } from "@bitwarden/common/platform/state"; +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 KeyDefinition( +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 KeyDefinition( +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/export.component.html b/apps/browser/src/tools/popup/settings/export.component.html deleted file mode 100644 index ef031b7979a..00000000000 --- a/apps/browser/src/tools/popup/settings/export.component.html +++ /dev/null @@ -1,35 +0,0 @@ -
-
-
- -
-

- {{ "exportVault" | i18n }} -

-
- -
-
-
- - {{ "personalVaultExportPolicyInEffect" | i18n }} - - - -
-
-
- - -
-
-
-
-
diff --git a/apps/browser/src/tools/popup/settings/export.component.ts b/apps/browser/src/tools/popup/settings/export.component.ts deleted file mode 100644 index 9f3f054d2ac..00000000000 --- a/apps/browser/src/tools/popup/settings/export.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; -import { Router } from "@angular/router"; - -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 { 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 { DialogService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; -import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui"; - -@Component({ - selector: "app-export", - templateUrl: "export.component.html", -}) -export class ExportComponent extends BaseExportComponent { - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - private router: Router, - logService: LogService, - formBuilder: UntypedFormBuilder, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - platformUtilsService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } - - protected saved() { - super.saved(); - // 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/export/export-browser-v2.component.html b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html new file mode 100644 index 00000000000..9ad6ed36835 --- /dev/null +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..cbb66cbcf5a --- /dev/null +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.ts @@ -0,0 +1,40 @@ +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"; + +import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; + +@Component({ + templateUrl: "export-browser-v2.component.html", + standalone: true, + imports: [ + CommonModule, + RouterLink, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ExportComponent, + PopupPageComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopOutComponent, + ], +}) +export class ExportBrowserV2Component { + protected disabled = false; + 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/export/export-browser.component.html b/apps/browser/src/tools/popup/settings/export/export-browser.component.html new file mode 100644 index 00000000000..bccde32a68d --- /dev/null +++ b/apps/browser/src/tools/popup/settings/export/export-browser.component.html @@ -0,0 +1,26 @@ +
+
+ +
+

+ {{ "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 new file mode 100644 index 00000000000..3125e0a2934 --- /dev/null +++ b/apps/browser/src/tools/popup/settings/export/export-browser.component.ts @@ -0,0 +1,40 @@ +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.html b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html new file mode 100644 index 00000000000..5458b46535a --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..16759057ed5 --- /dev/null +++ b/apps/browser/src/tools/popup/settings/import/import-browser-v2.component.ts @@ -0,0 +1,40 @@ +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"; + +import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; +import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-footer.component"; +import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; + +@Component({ + templateUrl: "import-browser-v2.component.html", + standalone: true, + imports: [ + CommonModule, + RouterLink, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ImportComponent, + PopupPageComponent, + PopupFooterComponent, + PopupHeaderComponent, + PopOutComponent, + ], +}) +export class ImportBrowserV2Component { + protected disabled = false; + protected loading = false; + + constructor(private router: Router) {} + + protected async onSuccessfulImport(organizationId: string): Promise { + await this.router.navigate(["/vault-settings"]); + } +} diff --git a/apps/browser/src/tools/popup/settings/settings.component.html b/apps/browser/src/tools/popup/settings/settings.component.html index 7dba3d0a3de..c547229653e 100644 --- a/apps/browser/src/tools/popup/settings/settings.component.html +++ b/apps/browser/src/tools/popup/settings/settings.component.html @@ -42,14 +42,6 @@
{{ "vault" | i18n }}
- - - {{ - "autofillSuggestionsTip" | i18n - }} - - 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 c00e585e739..eb8737d5139 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 @@ -3,12 +3,17 @@ import { Component } from "@angular/core"; import { combineLatest, map, Observable } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { IconButtonModule, SectionComponent, TypographyModule } from "@bitwarden/components"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { + IconButtonModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; -import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; +import { PopupCipherView } from "../../../views/popup-cipher.view"; import { VaultListItemsContainerComponent } from "../vault-list-items-container/vault-list-items-container.component"; @Component({ @@ -19,7 +24,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/ TypographyModule, VaultListItemsContainerComponent, JslibModule, - PopupSectionHeaderComponent, + SectionHeaderComponent, IconButtonModule, ], selector: "app-autofill-vault-list-items", @@ -30,7 +35,7 @@ export class AutofillVaultListItemsComponent { * The list of ciphers that can be used to autofill the current page. * @protected */ - protected autofillCiphers$: Observable = + protected autofillCiphers$: Observable = this.vaultPopupItemsService.autoFillCiphers$; /** @@ -41,7 +46,7 @@ export class AutofillVaultListItemsComponent { /** * Observable that determines whether the empty autofill tip should be shown. - * The tip is shown when there are no ciphers to autofill, no filter is applied, and autofill is allowed in + * The tip is shown when there are no login ciphers to autofill, no filter is applied, and autofill is allowed in * the current context (e.g. not in a popout). * @protected */ @@ -50,7 +55,10 @@ export class AutofillVaultListItemsComponent { this.autofillCiphers$, this.vaultPopupItemsService.autofillAllowed$, ]).pipe( - map(([hasFilter, ciphers, canAutoFill]) => !hasFilter && canAutoFill && ciphers.length === 0), + map( + ([hasFilter, ciphers, canAutoFill]) => + !hasFilter && canAutoFill && ciphers.filter((c) => c.type == CipherType.Login).length === 0, + ), ); constructor(private vaultPopupItemsService: VaultPopupItemsService) { 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 new file mode 100644 index 00000000000..08133c6b466 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts new file mode 100644 index 00000000000..c89fcca3b3f --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts @@ -0,0 +1,29 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components"; +import { CopyCipherFieldDirective } from "@bitwarden/vault"; + +@Component({ + standalone: true, + selector: "app-item-copy-actions", + templateUrl: "item-copy-actions.component.html", + imports: [ + ItemModule, + IconButtonModule, + JslibModule, + MenuModule, + CommonModule, + CopyCipherFieldDirective, + ], +}) +export class ItemCopyActionsComponent { + @Input() cipher: CipherView; + + protected CipherType = CipherType; + + constructor() {} +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html new file mode 100644 index 00000000000..1d7a2a8cd0c --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + {{ "clone" | i18n }} + + + + + 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 new file mode 100644 index 00000000000..ac3acdcc8fb --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -0,0 +1,138 @@ +import { CommonModule } from "@angular/common"; +import { booleanAttribute, Component, Input } from "@angular/core"; +import { Router, RouterModule } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + DialogService, + IconButtonModule, + ItemModule, + MenuModule, + ToastService, +} from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { BrowserApi } from "../../../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils"; +import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; + +@Component({ + standalone: true, + selector: "app-item-more-options", + templateUrl: "./item-more-options.component.html", + imports: [ItemModule, IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule], +}) +export class ItemMoreOptionsComponent { + @Input({ + required: true, + }) + cipher: CipherView; + + /** + * Flag to hide the login specific menu options. Used for login items that are + * already in the autofill list suggestion. + */ + @Input({ transform: booleanAttribute }) + hideLoginOptions: boolean; + + protected autofillAllowed$ = this.vaultPopupItemsService.autofillAllowed$; + + constructor( + private cipherService: CipherService, + private vaultPopupItemsService: VaultPopupItemsService, + private passwordRepromptService: PasswordRepromptService, + private toastService: ToastService, + private dialogService: DialogService, + private router: Router, + private i18nService: I18nService, + ) {} + + get canEdit() { + return this.cipher.edit; + } + + get isLogin() { + return this.cipher.type === CipherType.Login; + } + + get favoriteText() { + return this.cipher.favorite ? "unfavorite" : "favorite"; + } + + /** + * Determines if the login cipher can be launched in a new browser tab. + */ + get canLaunch() { + return this.isLogin && this.cipher.login.canLaunch; + } + + /** + * Launches the login cipher in a new browser tab. + */ + async launchCipher() { + if (!this.canLaunch) { + return; + } + + await this.cipherService.updateLastLaunchedDate(this.cipher.id); + + await BrowserApi.createNewTab(this.cipher.login.launchUri); + + if (BrowserPopupUtils.inPopup(window)) { + BrowserApi.closePopup(window); + } + } + + /** + * Toggles the favorite status of the cipher and updates it on the server. + */ + async toggleFavorite() { + this.cipher.favorite = !this.cipher.favorite; + const encryptedCipher = await this.cipherService.encrypt(this.cipher); + await this.cipherService.updateWithServer(encryptedCipher); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t( + this.cipher.favorite ? "itemAddedToFavorites" : "itemRemovedFromFavorites", + ), + }); + } + + /** + * Navigate to the clone cipher page with the current cipher as the source. + * A password reprompt is attempted if the cipher requires it. + * A confirmation dialog is shown if the cipher has FIDO2 credentials. + */ + async clone() { + if ( + this.cipher.reprompt === CipherRepromptType.Password && + !(await this.passwordRepromptService.showPasswordPrompt()) + ) { + return; + } + + if (this.cipher.login?.hasFido2Credentials) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "passkeyNotCopied" }, + content: { key: "passkeyNotCopiedAlert" }, + type: "info", + }); + + if (!confirmed) { + return; + } + } + + await this.router.navigate(["/clone-cipher"], { + queryParams: { + cloneMode: true, + cipherId: this.cipher.id, + }, + }); + } +} 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 new file mode 100644 index 00000000000..afb7dd40066 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.html @@ -0,0 +1,39 @@ +
+ + + + + + + + + + + + + + +
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts new file mode 100644 index 00000000000..886e1a966a8 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-filters/vault-list-filters.component.ts @@ -0,0 +1,28 @@ +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ChipSelectComponent } from "@bitwarden/components"; + +import { VaultPopupListFiltersService } from "../../../services/vault-popup-list-filters.service"; + +@Component({ + standalone: true, + selector: "app-vault-list-filters", + templateUrl: "./vault-list-filters.component.html", + imports: [CommonModule, JslibModule, ChipSelectComponent, ReactiveFormsModule], +}) +export class VaultListFiltersComponent implements OnDestroy { + protected filterForm = this.vaultPopupListFiltersService.filterForm; + protected organizations$ = this.vaultPopupListFiltersService.organizations$; + protected collections$ = this.vaultPopupListFiltersService.collections$; + protected folders$ = this.vaultPopupListFiltersService.folders$; + protected cipherTypes = this.vaultPopupListFiltersService.cipherTypes; + + constructor(private vaultPopupListFiltersService: VaultPopupListFiltersService) {} + + ngOnDestroy(): void { + this.vaultPopupListFiltersService.resetFilterForm(); + } +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index d3bb85c710f..7b8fdf7a8e5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -1,43 +1,48 @@ - - - {{ ciphers.length }} + + +

+ {{ title }} +

-
+ {{ ciphers.length }} + +
+ {{ description }} +
- + {{ cipher.name }} + {{ cipher.subTitle }} - + - - - - - - + + diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index f9b34e96162..0c810756352 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -3,17 +3,20 @@ import { booleanAttribute, Component, EventEmitter, Input, Output } from "@angul import { RouterLink } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { BadgeModule, ButtonModule, IconButtonModule, ItemModule, SectionComponent, + SectionHeaderComponent, TypographyModule, } from "@bitwarden/components"; -import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup-section-header/popup-section-header.component"; +import { PopupCipherView } from "../../../views/popup-cipher.view"; +import { ItemCopyActionsComponent } from "../item-copy-action/item-copy-actions.component"; +import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options.component"; @Component({ imports: [ @@ -25,8 +28,10 @@ import { PopupSectionHeaderComponent } from "../../../../../platform/popup/popup SectionComponent, TypographyModule, JslibModule, - PopupSectionHeaderComponent, + SectionHeaderComponent, RouterLink, + ItemCopyActionsComponent, + ItemMoreOptionsComponent, ], selector: "app-vault-list-items-container", templateUrl: "vault-list-items-container.component.html", @@ -37,7 +42,7 @@ export class VaultListItemsContainerComponent { * The list of ciphers to display. */ @Input() - ciphers: CipherView[]; + ciphers: PopupCipherView[] = []; /** * Title for the vault list item section. @@ -45,6 +50,13 @@ export class VaultListItemsContainerComponent { @Input() title: string; + /** + * Optional description for the vault list item section. Will be shown below the title even when + * no ciphers are available. + */ + @Input() + description: string; + /** * Option to show a refresh button in the section header. */ @@ -61,5 +73,19 @@ export class VaultListItemsContainerComponent { * Option to show the autofill button for each item. */ @Input({ transform: booleanAttribute }) - showAutoFill: boolean; + showAutofillButton: boolean; + + /** + * The tooltip text for the organization icon for ciphers that belong to an organization. + * @param cipher + */ + orgIconTooltip(cipher: PopupCipherView) { + if (cipher.collectionIds.length > 1) { + return this.i18nService.t("nCollections", cipher.collectionIds.length); + } + + return cipher.collections[0]?.name; + } + + constructor(private i18nService: I18nService) {} } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html new file mode 100644 index 00000000000..55674aa83e5 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.html @@ -0,0 +1,8 @@ +
+ + +
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts new file mode 100644 index 00000000000..e6100321607 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-search/vault-v2-search.component.ts @@ -0,0 +1,52 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormsModule } from "@angular/forms"; +import { Subject, Subscription, debounceTime, filter } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { SearchModule } from "@bitwarden/components"; + +import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; + +const SearchTextDebounceInterval = 200; + +@Component({ + imports: [CommonModule, SearchModule, JslibModule, FormsModule], + standalone: true, + selector: "app-vault-v2-search", + templateUrl: "vault-v2-search.component.html", +}) +export class VaultV2SearchComponent { + searchText: string; + + private searchText$ = new Subject(); + + constructor(private vaultPopupItemsService: VaultPopupItemsService) { + this.subscribeToLatestSearchText(); + this.subscribeToApplyFilter(); + } + + onSearchTextChanged() { + this.searchText$.next(this.searchText); + } + + subscribeToLatestSearchText(): Subscription { + return this.vaultPopupItemsService.latestSearchText$ + .pipe( + takeUntilDestroyed(), + filter((data) => !!data), + ) + .subscribe((text) => { + this.searchText = text; + }); + } + + subscribeToApplyFilter(): Subscription { + return this.searchText$ + .pipe(debounceTime(SearchTextDebounceInterval), takeUntilDestroyed()) + .subscribe((data) => { + this.vaultPopupItemsService.applyFilter(data); + }); + } +} 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 index 4d2674fd703..24ca030284f 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -14,8 +14,8 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co 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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; 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"; 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 index deb4434df47..b46b4cf9ff2 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -9,8 +9,8 @@ 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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; 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"; diff --git a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html index df2b2c1a13e..694c0e9be52 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault/vault-v2.component.html @@ -11,7 +11,10 @@ -
+
{{ "yourVaultIsEmpty" | i18n }} {{ "autofillSuggestionsTip" | i18n }} @@ -21,20 +24,31 @@
- - + + +
- + {{ "noItemsMatchSearch" | i18n }} {{ "clearFiltersOrTryAnother" | i18n }}
- +
+ + {{ "organizationIsDeactivated" | i18n }} + {{ "contactYourOrgAdmin" | i18n }} + +
+ + { + switch (true) { + case emptyVault: + this.vaultState = VaultState.Empty; + break; + case deactivatedOrg: + // The deactivated org state takes precedence over the no results state + this.vaultState = VaultState.DeactivatedOrg; + break; + case noResults: + this.vaultState = VaultState.NoResults; + break; + default: + this.vaultState = null; + } + }); + } ngOnInit(): void {} 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 1830d4be35e..b7091eb87bf 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 @@ -1,24 +1,41 @@ +import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductType } from "@bitwarden/common/enums"; +import { ObservableTracker } from "@bitwarden/common/spec"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { BrowserApi } from "../../../platform/browser/browser-api"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; import { VaultPopupItemsService } from "./vault-popup-items.service"; +import { VaultPopupListFiltersService } from "./vault-popup-list-filters.service"; describe("VaultPopupItemsService", () => { + let testBed: TestBed; let service: VaultPopupItemsService; let allCiphers: Record; let autoFillCiphers: CipherView[]; + let mockOrg: Organization; + let mockCollections: CollectionView[]; + const cipherServiceMock = mock(); const vaultSettingsServiceMock = mock(); + const organizationServiceMock = mock(); + const vaultPopupListFiltersServiceMock = mock(); + const searchService = mock(); + const collectionService = mock(); beforeEach(() => { allCiphers = cipherFactory(10); @@ -33,22 +50,109 @@ describe("VaultPopupItemsService", () => { cipherList[2].favorite = true; cipherList[3].favorite = true; - cipherServiceMock.cipherViews$ = new BehaviorSubject(allCiphers).asObservable(); - cipherServiceMock.filterCiphersForUrl.mockImplementation(async () => autoFillCiphers); - vaultSettingsServiceMock.showCardsCurrentTab$ = new BehaviorSubject(false).asObservable(); - vaultSettingsServiceMock.showIdentitiesCurrentTab$ = new BehaviorSubject(false).asObservable(); + cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); + cipherServiceMock.ciphers$ = new BehaviorSubject(null); + cipherServiceMock.localData$ = new BehaviorSubject(null); + searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); + cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => + ciphers.filter((c) => ["0", "1"].includes(c.id)), + ); + vaultSettingsServiceMock.showCardsCurrentTab$ = new BehaviorSubject(false); + vaultSettingsServiceMock.showIdentitiesCurrentTab$ = new BehaviorSubject(false); + + vaultPopupListFiltersServiceMock.filters$ = new BehaviorSubject({ + organization: null, + collection: null, + cipherType: null, + folder: null, + }); + // Return all ciphers, `filterFunction$` will be tested in `VaultPopupListFiltersService` + vaultPopupListFiltersServiceMock.filterFunction$ = new BehaviorSubject( + (ciphers: CipherView[]) => ciphers, + ); jest.spyOn(BrowserPopupUtils, "inPopout").mockReturnValue(false); jest .spyOn(BrowserApi, "getTabFromCurrentWindow") .mockResolvedValue({ url: "https://example.com" } as chrome.tabs.Tab); - service = new VaultPopupItemsService(cipherServiceMock, vaultSettingsServiceMock); + + mockOrg = { + id: "org1", + name: "Organization 1", + planProductType: ProductType.Enterprise, + } as Organization; + + mockCollections = [ + { id: "col1", name: "Collection 1" } as CollectionView, + { id: "col2", name: "Collection 2" } as CollectionView, + ]; + + organizationServiceMock.organizations$ = new BehaviorSubject([mockOrg]); + collectionService.decryptedCollections$ = new BehaviorSubject(mockCollections); + + testBed = TestBed.configureTestingModule({ + providers: [ + { provide: CipherService, useValue: cipherServiceMock }, + { provide: VaultSettingsService, useValue: vaultSettingsServiceMock }, + { provide: SearchService, useValue: searchService }, + { provide: OrganizationService, useValue: organizationServiceMock }, + { provide: VaultPopupListFiltersService, useValue: vaultPopupListFiltersServiceMock }, + { provide: CollectionService, useValue: collectionService }, + ], + }); + + service = testBed.inject(VaultPopupItemsService); + }); + + afterEach(() => { + jest.clearAllMocks(); }); it("should be created", () => { - service = new VaultPopupItemsService(cipherServiceMock, vaultSettingsServiceMock); + service = testBed.inject(VaultPopupItemsService); expect(service).toBeTruthy(); }); + it("should merge cipher views with collections and organization", (done) => { + const cipherList = Object.values(allCiphers); + cipherList[0].organizationId = "org1"; + cipherList[0].collectionIds = ["col1", "col2"]; + + service.autoFillCiphers$.subscribe((ciphers) => { + expect(ciphers[0].organization).toEqual(mockOrg); + expect(ciphers[0].collections).toContain(mockCollections[0]); + expect(ciphers[0].collections).toContain(mockCollections[1]); + done(); + }); + }); + + it("should update cipher list when cipherService.ciphers$ emits", async () => { + const tracker = new ObservableTracker(service.autoFillCiphers$); + + await tracker.expectEmission(); + + (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + + await tracker.expectEmission(); + + // Should only emit twice + expect(tracker.emissions.length).toBe(2); + await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded"); + }); + + it("should update cipher list when cipherService.localData$ emits", async () => { + const tracker = new ObservableTracker(service.autoFillCiphers$); + + await tracker.expectEmission(); + + (cipherServiceMock.localData$ as BehaviorSubject).next(null); + + await tracker.expectEmission(); + + // Should only emit twice + expect(tracker.emissions.length).toBe(2); + await expect(tracker.pauseUntilReceived(3)).rejects.toThrow("Timeout exceeded"); + }); + describe("autoFillCiphers$", () => { it("should return empty array if there is no current tab", (done) => { jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(null); @@ -69,12 +173,10 @@ describe("VaultPopupItemsService", () => { it("should filter ciphers for the current tab and types", (done) => { const currentTab = { url: "https://example.com" } as chrome.tabs.Tab; - vaultSettingsServiceMock.showCardsCurrentTab$ = new BehaviorSubject(true).asObservable(); - vaultSettingsServiceMock.showIdentitiesCurrentTab$ = new BehaviorSubject(true).asObservable(); + (vaultSettingsServiceMock.showCardsCurrentTab$ as BehaviorSubject).next(true); + (vaultSettingsServiceMock.showIdentitiesCurrentTab$ as BehaviorSubject).next(true); jest.spyOn(BrowserApi, "getTabFromCurrentWindow").mockResolvedValue(currentTab); - service = new VaultPopupItemsService(cipherServiceMock, vaultSettingsServiceMock); - service.autoFillCiphers$.subscribe((ciphers) => { expect(cipherServiceMock.filterCiphersForUrl.mock.calls.length).toBe(1); expect(cipherServiceMock.filterCiphersForUrl).toHaveBeenCalledWith( @@ -99,8 +201,6 @@ describe("VaultPopupItemsService", () => { Object.values(allCiphers), ); - service = new VaultPopupItemsService(cipherServiceMock, vaultSettingsServiceMock); - service.autoFillCiphers$.subscribe((ciphers) => { expect(ciphers.length).toBe(10); @@ -114,6 +214,23 @@ describe("VaultPopupItemsService", () => { done(); }); }); + + it("should filter autoFillCiphers$ down to search term", (done) => { + const searchText = "Login"; + + searchService.searchCiphers.mockImplementation(async (q, _, ciphers) => { + return ciphers.filter((cipher) => { + return cipher.name.includes(searchText); + }); + }); + + // there is only 1 Login returned for filteredCiphers. + service.autoFillCiphers$.subscribe((ciphers) => { + expect(ciphers[0].name.includes(searchText)).toBe(true); + expect(ciphers.length).toBe(1); + done(); + }); + }); }); describe("favoriteCiphers$", () => { @@ -131,6 +248,24 @@ describe("VaultPopupItemsService", () => { done(); }); }); + + it("should filter favoriteCiphers$ down to search term", (done) => { + const cipherList = Object.values(allCiphers); + const searchText = "Card 2"; + + searchService.searchCiphers.mockImplementation(async () => { + return cipherList.filter((cipher) => { + return cipher.name === searchText; + }); + }); + + service.favoriteCiphers$.subscribe((ciphers) => { + // There are 2 favorite items but only one Card 2 + expect(ciphers[0].name).toBe(searchText); + expect(ciphers.length).toBe(1); + done(); + }); + }); }); describe("remainingCiphers$", () => { @@ -148,12 +283,28 @@ describe("VaultPopupItemsService", () => { done(); }); }); + + it("should filter remainingCiphers$ down to search term", (done) => { + const cipherList = Object.values(allCiphers); + const searchText = "Login"; + + searchService.searchCiphers.mockImplementation(async () => { + return cipherList.filter((cipher) => { + return cipher.name.includes(searchText); + }); + }); + + service.remainingCiphers$.subscribe((ciphers) => { + // There are 6 remaining ciphers but only 2 with "Login" in the name + expect(ciphers.length).toBe(2); + done(); + }); + }); }); describe("emptyVault$", () => { it("should return true if there are no ciphers", (done) => { - cipherServiceMock.cipherViews$ = new BehaviorSubject({}).asObservable(); - service = new VaultPopupItemsService(cipherServiceMock, vaultSettingsServiceMock); + cipherServiceMock.getAllDecrypted.mockResolvedValue([]); service.emptyVault$.subscribe((empty) => { expect(empty).toBe(true); done(); @@ -192,6 +343,54 @@ describe("VaultPopupItemsService", () => { }); }); }); + + describe("noFilteredResults$", () => { + it("should return false when filteredResults has values", (done) => { + service.noFilteredResults$.subscribe((noResults) => { + expect(noResults).toBe(false); + done(); + }); + }); + + it("should return true when there are zero filteredResults", (done) => { + searchService.searchCiphers.mockImplementation(async () => []); + service.noFilteredResults$.subscribe((noResults) => { + expect(noResults).toBe(true); + done(); + }); + }); + }); + + describe("hasFilterApplied$", () => { + it("should return true if the search term provided is searchable", (done) => { + searchService.isSearchable.mockImplementation(async () => true); + service.hasFilterApplied$.subscribe((canSearch) => { + expect(canSearch).toBe(true); + done(); + }); + }); + + it("should return false if the search term provided is not searchable", (done) => { + searchService.isSearchable.mockImplementation(async () => false); + service.hasFilterApplied$.subscribe((canSearch) => { + expect(canSearch).toBe(false); + done(); + }); + }); + }); + + describe("applyFilter", () => { + it("should call search Service with the new search term", (done) => { + const searchText = "Hello"; + service.applyFilter(searchText); + const searchServiceSpy = jest.spyOn(searchService, "searchCiphers"); + + service.favoriteCiphers$.subscribe(() => { + expect(searchServiceSpy).toHaveBeenCalledWith(searchText, null, expect.anything()); + done(); + }); + }); + }); }); // A function to generate a list of ciphers of different types 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 52de117e6b5..189ce2c09f9 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,7 +1,11 @@ -import { Injectable } from "@angular/core"; +import { inject, Injectable, NgZone } from "@angular/core"; import { + BehaviorSubject, combineLatest, + distinctUntilKeyChanged, + from, map, + merge, Observable, of, shareReplay, @@ -10,13 +14,22 @@ import { switchMap, } from "rxjs"; +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import { runInsideAngular } from "../../../platform/browser/run-inside-angular.operator"; import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; +import { PopupCipherView } from "../views/popup-cipher.view"; + +import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-filters.service"; /** * Service for managing the various item lists on the new Vault tab in the browser popup. @@ -26,6 +39,8 @@ import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; }) export class VaultPopupItemsService { private _refreshCurrentTab$ = new Subject(); + private _searchText$ = new BehaviorSubject(""); + latestSearchText$: Observable = this._searchText$.asObservable(); /** * Observable that contains the list of other cipher types that should be shown @@ -64,9 +79,48 @@ export class VaultPopupItemsService { * Observable that contains the list of all decrypted ciphers. * @private */ - private _cipherList$: Observable = this.cipherService.cipherViews$.pipe( - map((ciphers) => Object.values(ciphers)), - shareReplay({ refCount: false, bufferSize: 1 }), + private _cipherList$: Observable = merge( + this.cipherService.ciphers$, + this.cipherService.localData$, + ).pipe( + runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular + switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), + 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.map( + (cipher) => + new PopupCipherView( + cipher, + cipher.collectionIds?.map((colId) => collectionMap[colId as CollectionId]), + orgMap[cipher.organizationId as OrganizationId], + ), + ); + }), + ), + ), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + private _filteredCipherList$: Observable = combineLatest([ + this._cipherList$, + this._searchText$, + this.vaultPopupListFiltersService.filterFunction$, + ]).pipe( + map(([ciphers, searchText, filterFunction]): [CipherView[], string] => [ + filterFunction(ciphers), + searchText, + ]), + switchMap( + ([ciphers, searchText]) => + this.searchService.searchCiphers(searchText, null, ciphers) as Promise, + ), + shareReplay({ refCount: true, bufferSize: 1 }), ); /** @@ -75,8 +129,8 @@ export class VaultPopupItemsService { * * See {@link refreshCurrentTab} to trigger re-evaluation of the current tab. */ - autoFillCiphers$: Observable = combineLatest([ - this._cipherList$, + autoFillCiphers$: Observable = combineLatest([ + this._filteredCipherList$, this._otherAutoFillTypes$, this._currentAutofillTab$, ]).pipe( @@ -94,9 +148,9 @@ export class VaultPopupItemsService { * List of favorite ciphers that are not currently suggested for autofill. * Ciphers are sorted by last used date, then by name. */ - favoriteCiphers$: Observable = combineLatest([ + favoriteCiphers$: Observable = combineLatest([ this.autoFillCiphers$, - this._cipherList$, + this._filteredCipherList$, ]).pipe( map(([autoFillCiphers, ciphers]) => ciphers.filter((cipher) => cipher.favorite && !autoFillCiphers.includes(cipher)), @@ -111,10 +165,10 @@ export class VaultPopupItemsService { * List of all remaining ciphers that are not currently suggested for autofill or marked as favorite. * Ciphers are sorted by name. */ - remainingCiphers$: Observable = combineLatest([ + remainingCiphers$: Observable = combineLatest([ this.autoFillCiphers$, this.favoriteCiphers$, - this._cipherList$, + this._filteredCipherList$, ]).pipe( map(([autoFillCiphers, favoriteCiphers, ciphers]) => ciphers.filter( @@ -127,9 +181,20 @@ export class VaultPopupItemsService { /** * Observable that indicates whether a filter is currently applied to the ciphers. - * @todo Implement filter/search functionality in PM-6824 and PM-6826. */ - hasFilterApplied$: Observable = of(false); + hasFilterApplied$ = combineLatest([ + this._searchText$, + this.vaultPopupListFiltersService.filters$, + ]).pipe( + switchMap(([searchText, filters]) => { + return from(this.searchService.isSearchable(searchText)).pipe( + map( + (isSearchable) => + isSearchable || Object.values(filters).some((filter) => filter !== null), + ), + ); + }), + ); /** * Observable that indicates whether autofill is allowed in the current context. @@ -144,13 +209,33 @@ export class VaultPopupItemsService { /** * Observable that indicates whether there are no ciphers to show with the current filter. - * @todo Implement filter/search functionality in PM-6824 and PM-6826. */ - noFilteredResults$: Observable = of(false); + noFilteredResults$: Observable = this._filteredCipherList$.pipe( + map((ciphers) => !ciphers.length), + ); + + /** Observable that indicates when the user should see the deactivated org state */ + showDeactivatedOrg$: Observable = combineLatest([ + this.vaultPopupListFiltersService.filters$.pipe(distinctUntilKeyChanged("organization")), + this.organizationService.organizations$, + ]).pipe( + map(([filters, orgs]) => { + if (!filters.organization || filters.organization.id === MY_VAULT_ID) { + return false; + } + + const org = orgs.find((o) => o.id === filters.organization.id); + return org ? !org.enabled : false; + }), + ); constructor( private cipherService: CipherService, private vaultSettingsService: VaultSettingsService, + private vaultPopupListFiltersService: VaultPopupListFiltersService, + private organizationService: OrganizationService, + private searchService: SearchService, + private collectionService: CollectionService, ) {} /** @@ -160,6 +245,10 @@ export class VaultPopupItemsService { this._refreshCurrentTab$.next(null); } + applyFilter(newSearchText: string) { + this._searchText$.next(newSearchText); + } + /** * Sort function for ciphers to be used in the autofill section of the Vault tab. * Sorts by type, then by last used date, and finally by name. 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 new file mode 100644 index 00000000000..907ff9af8d6 --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -0,0 +1,383 @@ +import { TestBed } from "@angular/core/testing"; +import { FormBuilder } from "@angular/forms"; +import { BehaviorSubject, skipWhile } from "rxjs"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { Collection } from "@bitwarden/common/vault/models/domain/collection"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; + +import { MY_VAULT_ID, VaultPopupListFiltersService } from "./vault-popup-list-filters.service"; + +describe("VaultPopupListFiltersService", () => { + let service: VaultPopupListFiltersService; + const memberOrganizations$ = new BehaviorSubject([]); + const folderViews$ = new BehaviorSubject([]); + const cipherViews$ = new BehaviorSubject({}); + const decryptedCollections$ = new BehaviorSubject([]); + + const collectionService = { + decryptedCollections$, + getAllNested: () => Promise.resolve([]), + } as unknown as CollectionService; + + const folderService = { + folderViews$, + } as unknown as FolderService; + + const cipherService = { + cipherViews$, + } as unknown as CipherService; + + const organizationService = { + memberOrganizations$, + } as unknown as OrganizationService; + + const i18nService = { + t: (key: string) => key, + } as I18nService; + + beforeEach(() => { + memberOrganizations$.next([]); + decryptedCollections$.next([]); + + collectionService.getAllNested = () => Promise.resolve([]); + TestBed.configureTestingModule({ + providers: [ + { + provide: FolderService, + useValue: folderService, + }, + { + provide: CipherService, + useValue: cipherService, + }, + { + provide: OrganizationService, + useValue: organizationService, + }, + { + provide: I18nService, + useValue: i18nService, + }, + { + provide: CollectionService, + useValue: collectionService, + }, + { provide: FormBuilder, useClass: FormBuilder }, + ], + }); + + service = TestBed.inject(VaultPopupListFiltersService); + }); + + describe("cipherTypes", () => { + it("returns all cipher types", () => { + expect(service.cipherTypes.map((c) => c.value)).toEqual([ + CipherType.Login, + CipherType.Card, + CipherType.Identity, + CipherType.SecureNote, + ]); + }); + }); + + describe("organizations$", () => { + it('does not add "myVault" to the list of organizations when there are no organizations', (done) => { + memberOrganizations$.next([]); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.label)).toEqual([]); + done(); + }); + }); + + it('adds "myVault" to the list of organizations when there are other organizations', (done) => { + const orgs = [{ name: "bobby's org", id: "1234-3323-23223" }] as Organization[]; + memberOrganizations$.next(orgs); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.label)).toEqual(["myVault", "bobby's org"]); + done(); + }); + }); + + it("sorts organizations by name", (done) => { + const orgs = [ + { name: "bobby's org", id: "1234-3323-23223" }, + { name: "alice's org", id: "2223-4343-99888" }, + ] as Organization[]; + memberOrganizations$.next(orgs); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.label)).toEqual([ + "myVault", + "alice's org", + "bobby's org", + ]); + done(); + }); + }); + + describe("icons", () => { + it("sets family icon for family organizations", (done) => { + const orgs = [ + { + name: "family org", + id: "1234-3323-23223", + enabled: true, + planProductType: ProductType.Families, + }, + ] as Organization[]; + + memberOrganizations$.next(orgs); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]); + done(); + }); + }); + + it("sets family icon for free organizations", (done) => { + const orgs = [ + { + name: "free org", + id: "1234-3323-23223", + enabled: true, + planProductType: ProductType.Free, + }, + ] as Organization[]; + + memberOrganizations$.next(orgs); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.icon)).toEqual(["bwi-user", "bwi-family"]); + done(); + }); + }); + + it("sets warning icon for disabled organizations", (done) => { + const orgs = [ + { + name: "free org", + id: "1234-3323-23223", + enabled: false, + planProductType: ProductType.Free, + }, + ] as Organization[]; + + memberOrganizations$.next(orgs); + + service.organizations$.subscribe((organizations) => { + expect(organizations.map((o) => o.icon)).toEqual([ + "bwi-user", + "bwi-exclamation-triangle tw-text-danger", + ]); + done(); + }); + }); + }); + }); + + describe("collections$", () => { + const testCollection = { + id: "14cbf8e9-7a2a-4105-9bf6-b15c01203cef", + name: "Test collection", + organizationId: "3f860945-b237-40bc-a51e-b15c01203ccf", + } as CollectionView; + + const testCollection2 = { + id: "b15c0120-7a2a-4105-9bf6-b15c01203ceg", + name: "Test collection 2", + organizationId: "1203ccf-2432-123-acdd-b15c01203ccf", + } as CollectionView; + + const testCollections = [testCollection, testCollection2]; + + beforeEach(() => { + decryptedCollections$.next(testCollections); + + collectionService.getAllNested = () => + Promise.resolve( + testCollections.map((c) => ({ + children: [], + node: c, + parent: null, + })), + ); + }); + + it("returns all collections", (done) => { + service.collections$.subscribe((collections) => { + expect(collections.map((c) => c.label)).toEqual(["Test collection", "Test collection 2"]); + done(); + }); + }); + + it("filters out collections that do not belong to an organization", () => { + service.filterForm.patchValue({ + organization: { id: testCollection2.organizationId } as Organization, + }); + + service.collections$.subscribe((collections) => { + expect(collections.map((c) => c.label)).toEqual(["Test collection 2"]); + }); + }); + + it("sets collection icon", (done) => { + service.collections$.subscribe((collections) => { + expect(collections.every(({ icon }) => icon === "bwi-collection")).toBeTruthy(); + done(); + }); + }); + }); + + describe("folders$", () => { + it('returns no folders when "No Folder" is the only option', (done) => { + folderViews$.next([{ id: null, name: "No Folder" }]); + + service.folders$.subscribe((folders) => { + expect(folders).toEqual([]); + done(); + }); + }); + + it('moves "No Folder" to the end of the list', (done) => { + folderViews$.next([ + { id: null, name: "No Folder" }, + { id: "2345", name: "Folder 2" }, + { id: "1234", name: "Folder 1" }, + ]); + + service.folders$.subscribe((folders) => { + expect(folders.map((f) => f.label)).toEqual(["Folder 1", "Folder 2", "itemsWithNoFolder"]); + done(); + }); + }); + + it("returns all folders when MyVault is selected", (done) => { + service.filterForm.patchValue({ + organization: { id: MY_VAULT_ID } as Organization, + }); + + folderViews$.next([ + { id: "1234", name: "Folder 1" }, + { id: "2345", name: "Folder 2" }, + ]); + + service.folders$.subscribe((folders) => { + expect(folders.map((f) => f.label)).toEqual(["Folder 1", "Folder 2"]); + done(); + }); + }); + + it("sets folder icon", (done) => { + service.filterForm.patchValue({ + organization: { id: MY_VAULT_ID } as Organization, + }); + + folderViews$.next([ + { id: "1234", name: "Folder 1" }, + { id: "2345", name: "Folder 2" }, + ]); + + service.folders$.subscribe((folders) => { + expect(folders.every(({ icon }) => icon === "bwi-folder")).toBeTruthy(); + done(); + }); + }); + + it("returns folders that have ciphers within the selected organization", (done) => { + service.folders$.pipe(skipWhile((folders) => folders.length === 2)).subscribe((folders) => { + expect(folders.map((f) => f.label)).toEqual(["Folder 1"]); + done(); + }); + + service.filterForm.patchValue({ + organization: { id: "1234" } as Organization, + }); + + folderViews$.next([ + { id: "1234", name: "Folder 1" }, + { id: "2345", name: "Folder 2" }, + ]); + + cipherViews$.next({ + "1": { folderId: "1234", organizationId: "1234" }, + "2": { folderId: "2345", organizationId: "56789" }, + }); + }); + }); + + describe("filterFunction$", () => { + const ciphers = [ + { type: CipherType.Login, collectionIds: [], organizationId: null }, + { type: CipherType.Card, collectionIds: ["1234"], organizationId: "8978" }, + { type: CipherType.Identity, collectionIds: [], folderId: "5432", organizationId: null }, + { type: CipherType.SecureNote, collectionIds: [], organizationId: null }, + ] as CipherView[]; + + it("filters by cipherType", (done) => { + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(ciphers)).toEqual([ciphers[0]]); + done(); + }); + + service.filterForm.patchValue({ cipherType: CipherType.Login }); + }); + + it("filters by collection", (done) => { + const collection = { id: "1234" } as Collection; + + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(ciphers)).toEqual([ciphers[1]]); + done(); + }); + + service.filterForm.patchValue({ collection }); + }); + + it("filters by folder", (done) => { + const folder = { id: "5432" } as FolderView; + + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(ciphers)).toEqual([ciphers[2]]); + done(); + }); + + service.filterForm.patchValue({ folder }); + }); + + describe("organizationId", () => { + it("filters out ciphers that belong to an organization when MyVault is selected", (done) => { + const organization = { id: MY_VAULT_ID } as Organization; + + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(ciphers)).toEqual([ciphers[0], ciphers[2], ciphers[3]]); + done(); + }); + + service.filterForm.patchValue({ organization }); + }); + + it("filters out ciphers that do not belong to the selected organization", (done) => { + const organization = { id: "8978" } as Organization; + + service.filterFunction$.subscribe((filterFunction) => { + expect(filterFunction(ciphers)).toEqual([ciphers[1]]); + done(); + }); + + service.filterForm.patchValue({ organization }); + }); + }); + }); +}); 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 new file mode 100644 index 00000000000..6406e43446d --- /dev/null +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -0,0 +1,379 @@ +import { Injectable } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { FormBuilder } from "@angular/forms"; +import { + Observable, + combineLatest, + distinctUntilChanged, + map, + startWith, + switchMap, + tap, +} from "rxjs"; + +import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { Collection } from "@bitwarden/common/vault/models/domain/collection"; +import { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; +import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { ServiceUtils } from "@bitwarden/common/vault/service-utils"; +import { ChipSelectOption } from "@bitwarden/components"; + +/** All available cipher filters */ +export type PopupListFilter = { + organization: Organization | null; + collection: Collection | null; + folder: FolderView | null; + cipherType: CipherType | null; +}; + +/** Delimiter that denotes a level of nesting */ +const NESTING_DELIMITER = "/"; + +/** Id assigned to the "My vault" organization */ +export const MY_VAULT_ID = "MyVault"; + +const INITIAL_FILTERS: PopupListFilter = { + organization: null, + collection: null, + folder: null, + cipherType: null, +}; + +@Injectable({ + providedIn: "root", +}) +export class VaultPopupListFiltersService { + /** + * UI form for all filters + */ + filterForm = this.formBuilder.group(INITIAL_FILTERS); + + /** + * Observable for `filterForm` value + */ + filters$ = this.filterForm.valueChanges.pipe( + startWith(INITIAL_FILTERS), + ) as Observable; + + /** + * Static list of ciphers views used in synchronous context + */ + private cipherViews: CipherView[] = []; + + /** + * Observable of cipher views + */ + private cipherViews$: Observable = this.cipherService.cipherViews$.pipe( + tap((cipherViews) => { + this.cipherViews = Object.values(cipherViews); + }), + map((ciphers) => Object.values(ciphers)), + ); + + constructor( + private folderService: FolderService, + private cipherService: CipherService, + private organizationService: OrganizationService, + private i18nService: I18nService, + private collectionService: CollectionService, + private formBuilder: FormBuilder, + ) { + this.filterForm.controls.organization.valueChanges + .pipe(takeUntilDestroyed()) + .subscribe(this.validateOrganizationChange.bind(this)); + } + + /** + * Observable whose value is a function that filters an array of `CipherView` objects based on the current filters + */ + filterFunction$: Observable<(ciphers: CipherView[]) => CipherView[]> = this.filters$.pipe( + map( + (filters) => (ciphers: CipherView[]) => + ciphers.filter((cipher) => { + if (filters.cipherType !== null && cipher.type !== filters.cipherType) { + return false; + } + + if ( + filters.collection !== null && + !cipher.collectionIds.includes(filters.collection.id) + ) { + return false; + } + + if (filters.folder !== null && cipher.folderId !== filters.folder.id) { + return false; + } + + const isMyVault = filters.organization?.id === MY_VAULT_ID; + + if (isMyVault) { + if (cipher.organizationId !== null) { + return false; + } + } else if (filters.organization !== null) { + if (cipher.organizationId !== filters.organization.id) { + return false; + } + } + + return true; + }), + ), + ); + + /** + * All available cipher types + */ + readonly cipherTypes: ChipSelectOption[] = [ + { + value: CipherType.Login, + label: this.i18nService.t("typeLogin"), + icon: "bwi-globe", + }, + { + value: CipherType.Card, + label: this.i18nService.t("typeCard"), + icon: "bwi-credit-card", + }, + { + value: CipherType.Identity, + label: this.i18nService.t("typeIdentity"), + icon: "bwi-id-card", + }, + { + value: CipherType.SecureNote, + label: this.i18nService.t("note"), + icon: "bwi-sticky-note", + }, + ]; + + /** Resets `filterForm` to the original state */ + resetFilterForm(): void { + this.filterForm.reset(INITIAL_FILTERS); + } + + /** + * Organization array structured to be directly passed to `ChipSelectComponent` + */ + organizations$: Observable[]> = + this.organizationService.memberOrganizations$.pipe( + map((orgs) => orgs.sort(Utils.getSortFunction(this.i18nService, "name"))), + map((orgs) => { + if (!orgs.length) { + return []; + } + + return [ + // When the user is a member of an organization, make the "My Vault" option available + { + value: { id: MY_VAULT_ID } as Organization, + label: this.i18nService.t("myVault"), + icon: "bwi-user", + }, + ...orgs.map((org) => { + let icon = "bwi-business"; + + if (!org.enabled) { + // Show a warning icon if the organization is deactivated + icon = "bwi-exclamation-triangle tw-text-danger"; + } else if ( + org.planProductType === ProductType.Families || + org.planProductType === ProductType.Free + ) { + // Show a family icon if the organization is a family or free org + icon = "bwi-family"; + } + + return { + value: org, + label: org.name, + icon, + }; + }), + ]; + }), + ); + + /** + * 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]; + } + + // Sort folders by alphabetic name + folders.sort(Utils.getSortFunction(this.i18nService, "name")); + let arrangedFolders = folders; + + 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"); + + // 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; + + // 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); + + // 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")), + ), + ); + + /** + * Collection array structured to be directly passed to `ChipSelectComponent` + */ + collections$: Observable[]> = combineLatest([ + this.filters$.pipe( + distinctUntilChanged( + (previousFilter, currentFilter) => + // Only update the collections when the organizationId filter changes + previousFilter.organization?.id === currentFilter.organization?.id, + ), + ), + this.collectionService.decryptedCollections$, + ]).pipe( + map(([filters, allCollections]) => { + const organizationId = filters.organization?.id ?? null; + // When the organization filter is selected, filter out collections that do not belong to the selected organization + const collections = + organizationId === null + ? allCollections + : allCollections.filter((c) => c.organizationId === organizationId); + + return collections; + }), + switchMap(async (collections) => { + const nestedCollections = await this.collectionService.getAllNested(collections); + + return new DynamicTreeNode({ + fullList: collections, + nestedList: nestedCollections, + }); + }), + map((collections) => + collections.nestedList.map((c) => this.convertToChipSelectOption(c, "bwi-collection")), + ), + ); + + /** + * Converts the given item into the `ChipSelectOption` structure + */ + private convertToChipSelectOption( + item: TreeNode, + icon: string, + ): ChipSelectOption { + return { + value: item.node, + label: item.node.name, + icon, + children: item.children + ? item.children.map((i) => this.convertToChipSelectOption(i, icon)) + : undefined, + }; + } + + /** + * Returns a nested folder structure based on the input FolderView array + */ + private getAllFoldersNested(folders: FolderView[]): TreeNode[] { + const nodes: TreeNode[] = []; + + folders.forEach((f) => { + const folderCopy = new FolderView(); + folderCopy.id = f.id; + folderCopy.revisionDate = f.revisionDate; + + // Remove "/" from beginning and end of the folder name + // then split the folder name by the delimiter + const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NESTING_DELIMITER) : []; + ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NESTING_DELIMITER); + }); + + return nodes; + } + + /** + * Validate collection & folder filters when the organization filter changes + */ + private validateOrganizationChange(organization: Organization | null): void { + if (!organization) { + return; + } + + const currentFilters = this.filterForm.getRawValue(); + + // When the organization filter changes and a collection is already selected, + // reset the collection filter if the collection does not belong to the new organization filter + if (currentFilters.collection && currentFilters.collection.organizationId !== organization.id) { + this.filterForm.get("collection").setValue(null); + } + + // When the organization filter changes and a folder is already selected, + // reset the folder filter if the folder does not belong to the new organization filter + if ( + currentFilters.folder && + currentFilters.folder.id !== null && + organization.id !== MY_VAULT_ID + ) { + // Get all ciphers that belong to the new organization + const orgCiphers = this.cipherViews.filter((c) => c.organizationId === organization.id); + + // Find any ciphers within the organization that belong to the current folder + const newOrgContainsFolder = orgCiphers.some( + (oc) => oc.folderId === currentFilters.folder.id, + ); + + // If the new organization does not contain the current folder, reset the folder filter + if (!newOrgContainsFolder) { + this.filterForm.get("folder").setValue(null); + } + } + } +} diff --git a/apps/browser/src/vault/popup/settings/sync.component.ts b/apps/browser/src/vault/popup/settings/sync.component.ts index 3fe4de9eb51..16f388804bb 100644 --- a/apps/browser/src/vault/popup/settings/sync.component.ts +++ b/apps/browser/src/vault/popup/settings/sync.component.ts @@ -2,7 +2,7 @@ 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/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; @Component({ selector: "app-sync", diff --git a/apps/browser/src/vault/popup/views/popup-cipher.view.ts b/apps/browser/src/vault/popup/views/popup-cipher.view.ts new file mode 100644 index 00000000000..4707eb9eb0f --- /dev/null +++ b/apps/browser/src/vault/popup/views/popup-cipher.view.ts @@ -0,0 +1,41 @@ +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { ProductType } from "@bitwarden/common/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; + +/** + * Extended cipher view for the popup. Includes the associated collections and organization + * if applicable. + */ +export class PopupCipherView extends CipherView { + collections?: CollectionView[]; + organization?: Organization; + + constructor( + cipher: CipherView, + collections: CollectionView[] = null, + organization: Organization = null, + ) { + super(); + Object.assign(this, cipher); + this.collections = collections; + this.organization = organization; + } + + /** + * Get the bwi icon for the cipher according to the organization type. + */ + get orgIcon(): "bwi-family" | "bwi-business" | null { + switch (this.organization?.planProductType) { + case ProductType.Free: + case ProductType.Families: + return "bwi-family"; + case ProductType.Teams: + case ProductType.Enterprise: + case ProductType.TeamsStarter: + return "bwi-business"; + default: + return null; + } + } +} 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 43a28928da5..17934b3867f 100644 --- a/apps/browser/src/vault/services/vault-browser-state.service.ts +++ b/apps/browser/src/vault/services/vault-browser-state.service.ts @@ -3,28 +3,31 @@ import { Jsonify } from "type-fest"; import { ActiveUserState, - KeyDefinition, StateProvider, + UserKeyDefinition, VAULT_BROWSER_MEMORY, } from "@bitwarden/common/platform/state"; import { BrowserComponentState } from "../../models/browserComponentState"; import { BrowserGroupingsComponentState } from "../../models/browserGroupingsComponentState"; -export const VAULT_BROWSER_GROUPINGS_COMPONENT = new KeyDefinition( - VAULT_BROWSER_MEMORY, - "vault_browser_groupings_component", - { - deserializer: (obj: Jsonify) => - BrowserGroupingsComponentState.fromJSON(obj), - }, -); +export const VAULT_BROWSER_GROUPINGS_COMPONENT = + new UserKeyDefinition( + VAULT_BROWSER_MEMORY, + "vault_browser_groupings_component", + { + deserializer: (obj: Jsonify) => + BrowserGroupingsComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], + }, + ); -export const VAULT_BROWSER_COMPONENT = new KeyDefinition( +export const VAULT_BROWSER_COMPONENT = new UserKeyDefinition( VAULT_BROWSER_MEMORY, "vault_browser_component", { deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), + clearOn: ["logout", "lock"], }, ); diff --git a/apps/browser/store/locales/ja/copy.resx b/apps/browser/store/locales/ja/copy.resx index 910c8d7cab5..3ecba765d01 100644 --- a/apps/browser/store/locales/ja/copy.resx +++ b/apps/browser/store/locales/ja/copy.resx @@ -124,48 +124,47 @@ 自宅、職場、または外出先でも、Bitwarden はすべてのパスワード、パスキー、機密情報を簡単に保護します。 - Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! + PCMag、WIRED、The Verge、CNET、G2 などから最高のパスワードマネージャーとして認められています! -SECURE YOUR DIGITAL LIFE -Secure your digital life and protect against data breaches by generating and saving unique, strong passwords for every account. Maintain everything in an end-to-end encrypted password vault that only you can access. +デジタルライフを守る +データ漏洩を防ぐために、各アカウントに対してユニークで強力なパスワードを生成し保存することで、デジタルライフを守りましょう。エンドツーエンドで暗号化されたパスワード保管庫にすべてを保存し、あなただけがアクセスできます。 -ACCESS YOUR DATA, ANYWHERE, ANYTIME, ON ANY DEVICE -Easily manage, store, secure, and share unlimited passwords across unlimited devices without restrictions. +どこでも、いつでも、どのデバイスでもデータにアクセス +デバイスやパスワード数の制限は一切なく、簡単に管理、保存、保護、共有できます。 -EVERYONE SHOULD HAVE THE TOOLS TO STAY SAFE ONLINE -Utilize Bitwarden for free with no ads or selling data. Bitwarden believes everyone should have the ability to stay safe online. Premium plans offer access to advanced features. +誰もがオンラインで安全を保つためのツールを持つべき +Bitwarden は広告やデータ販売なしに無料で利用できます。Bitwarden は、誰もがオンラインで安全を保つ能力を持つべきだと信じています。プレミアムプランでは高度な機能にアクセスできます。 -EMPOWER YOUR TEAMS WITH BITWARDEN -Plans for Teams and Enterprise come with professional business features. Some examples include SSO integration, self-hosting, directory integration and SCIM provisioning, global policies, API access, event logs, and more. +チームを Bitwarden で強化 +チームおよびエンタープライズ向けのプランには、SSO 統合、セルフホスティング、ディレクトリ統合と SCIM プロビジョニング、グローバルポリシー、API アクセス、イベントログなどのプロフェッショナルなビジネス機能が含まれます。 -Use Bitwarden to secure your workforce and share sensitive information with colleagues. +Bitwardenを使用して、従業員を保護し、同僚と機密情報を共有しましょう。 +Bitwardenを選ぶ理由 -More reasons to choose Bitwarden: +世界クラスの暗号化 +パスワードは、先進的なエンドツーエンド暗号化(AES-256ビット、ソルト付きハッシュタグ、PBKDF2 SHA-256)で保護され、データは安全かつプライベートに保たれます。 -World-Class Encryption -Passwords are protected with advanced end-to-end encryption (AES-256 bit, salted hashtag, and PBKDF2 SHA-256) so your data stays secure and private. +第三者監査 +Bitwarden は、著名なセキュリティ企業による包括的な第三者セキュリティ監査を定期的に実施しています。これらの年次監査には、ソースコードの評価や Bitwarden の IP、サーバー、ウェブアプリケーション全体にわたるペネトレーションテストが含まれます。 -3rd-party Audits -Bitwarden regularly conducts comprehensive third-party security audits with notable security firms. These annual audits include source code assessments and penetration testing across Bitwarden IPs, servers, and web applications. - -Advanced 2FA -Secure your login with a third-party authenticator, emailed codes, or FIDO2 WebAuthn credentials such as a hardware security key or passkey. +高度な2要素認証 +サードパーティーの認証アプリ、メールコード、またはハードウェアセキュリティキーやパスキーなどの FIDO2 WebAuthn 資格情報でログインを保護します。 Bitwarden Send -Transmit data directly to others while maintaining end-to-end encrypted security and limiting exposure. +エンドツーエンドで暗号化されたセキュリティを維持しながら、他者にデータを直接送信し、データ漏洩を制限します。 -Built-in Generator -Create long, complex, and distinct passwords and unique usernames for every site you visit. Integrate with email alias providers for additional privacy. +内蔵ジェネレーター +訪問するすべてのサイトに対して、長く複雑でユニークなパスワードとユーザー名を作成できます。追加のプライバシーのためにメールエイリアスプロバイダーと統合することもできます。 -Global Translations -Bitwarden translations exist for more than 60 languages, translated by the global community though Crowdin. +グローバル翻訳 +Bitwarden の翻訳は、Crowdin を通じてグローバルコミュニティによって翻訳され、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 のエンドツーエンド暗号化された資格情報管理ソリューションは、組織が開発者のシークレットやパスキー体験を含むすべてを保護することを支援します。Bitwarden シークレットマネージャーや Bitwarden Passwordless.dev について詳しくは、Bitwarden.com をご覧ください。 diff --git a/apps/browser/store/locales/sv/copy.resx b/apps/browser/store/locales/sv/copy.resx index 6406ab013e6..af48fec33bf 100644 --- a/apps/browser/store/locales/sv/copy.resx +++ b/apps/browser/store/locales/sv/copy.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Bitwarden Password Manager + Bitwarden Lösenordshanterare - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Hemma, på jobbet eller på resande fot säkrar Bitwarden enkelt alla dina lösenord, passkeys, och känslig information. Recognized as the best password manager by PCMag, WIRED, The Verge, CNET, G2, and more! @@ -169,7 +169,7 @@ End-to-end encrypted credential management solutions from Bitwarden empower orga - At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. + Hemma, på jobbet eller på resande fot säkrar Bitwarden enkelt alla dina lösenord, passkeys, och känslig information. Synkronisera och kom åt ditt valv från flera enheter diff --git a/apps/browser/store/locales/zh_CN/copy.resx b/apps/browser/store/locales/zh_CN/copy.resx index d010cb1a7bf..e864e27ed0b 100644 --- a/apps/browser/store/locales/zh_CN/copy.resx +++ b/apps/browser/store/locales/zh_CN/copy.resx @@ -124,21 +124,21 @@ 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 - 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳密码管理器! + 被 PCMag、WIRED、The Verge、CNET、G2 等评为最佳的密码管理器! 保护您的数字生活 -通过为每个账户生成并保存独特而强大的密码,保护您的数字生活并防范数据泄露。所有内容保存在只有您可以访问的端对端加密的密码库中。 +通过为每个账户生成并保存唯一而强大的密码,保护您的数字生活并防止数据泄露。所有内容保存在只有您可以访问的端对端加密的密码库中。 随时随地在任何设备上访问您的数据 -不受任何限制跨无限数量的设备轻松管理、存储、保护和分享不限数量的密码。 +不受任何限制,跨无限数量的设备,轻松地管理、存储、保护和分享不限数量的密码。 每个人都应该拥有的保持在线安全的工具 -使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了堆高级功能的访问。 +使用 Bitwarden 是免费的,没有广告,不会出售数据。Bitwarden 相信每个人都应该拥有保持在线安全的能力。高级计划提供了对高级功能的访问。 -通过 BITWARDEN 为您的团队提供支持 -团队和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成和 SCIM 配置、全局策略、API 访问、事件日志等。 +使用 BITWARDEN 为您的团队提供支持 +团队计划和企业计划具有专业的商业功能。例如 SSO 集成、自托管、目录集成,以及 SCIM 配置、全局策略、API 访问、事件日志等。 -使用 Bitwarden 保护您的团队,并与同事共享敏感信息。 +使用 Bitwarden 保护您的劳动成果,并与同事共享敏感信息。 选择 Bitwarden 的更多理由: @@ -146,25 +146,25 @@ 密码受到先进的端对端加密(AES-256 位、加盐哈希标签和 PBKDF2 SHA-256)保护,使您的数据保持安全和私密。 第三方审计 -Bitwarden 定期与知名的安全公司进行全面的第三方安全审计。这些年度审核包括对 Bitwarden IP、服务器和 Web 应用程序的源代码评估和渗透测试。 +Bitwarden 定期与知名的安全公司进行全面的第三方安全审计。这些年度审计包括对 Bitwarden IP、服务器和 Web 应用程序的源代码评估和渗透测试。 -高级两步验证 -使用第三方身份验证器、通过电子邮件发送代码或 FIDO2 WebAuthn 凭据(如硬件安全钥匙或通行密钥)保护您的登录。 +高级 2FA +使用第三方验证器、电子邮件代码或 FIDO2 WebAuthn 凭据(例如硬件安全钥匙或通行密钥)保护您的登录。 Bitwarden Send -直接传输数据给他人,同时保持端对端加密的安全性并防止曝露。 +直接传输数据给他人,同时保持端对端加密的安全性并防止暴露。 内置生成器 -为您访问的每个网站创建长、复杂且独特的密码和用户名。与电子邮件别名提供商集成,增加隐私保护。 +为您访问的每个网站创建足够长、足够复杂且唯一的密码和用户名。与电子邮件别名提供商集成,增加隐私保护。 全球翻译 -Bitwarden 的翻译涵盖 60 多种语言,由全球社区通过 Crowdin 翻译。 +Bitwarden 的翻译涵盖 60 多种语言,由全球社区使用 Crowdin 翻译。 跨平台应用程序 从任何浏览器、移动设备或桌面操作系统中安全地访问和共享 Bitwarden 密码库中的敏感数据。 Bitwarden 保护的不仅仅是密码 -Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! +Bitwarden 的端对端加密凭据管理解决方案使组织能够保护所有内容,包括开发人员机密和通行密钥体验。访问 Bitwarden.com 了解更多关于 Bitwarden Secrets Manager 和 Bitwarden Passwordless.dev 的信息! 无论是在家里、工作中还是在外出时,Bitwarden 都可以轻松地保护您的所有密码、通行密钥和敏感信息。 diff --git a/apps/browser/test.setup.ts b/apps/browser/test.setup.ts index 4800b4c17f3..5435c6fd7fe 100644 --- a/apps/browser/test.setup.ts +++ b/apps/browser/test.setup.ts @@ -1,3 +1,5 @@ +import "jest-preset-angular/setup-jest"; + // Add chrome storage api const QUOTA_BYTES = 10; const storage = { diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index e1bf2b7211c..39f9c8211c7 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -19,6 +19,9 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], + "@bitwarden/generator-extensions": ["../../libs/tools/generator/extensions/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], @@ -26,6 +29,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"] }, "useDefineForClassFields": false diff --git a/apps/browser/webpack.config.js b/apps/browser/webpack.config.js index 2756ab4395f..eb1244bc26d 100644 --- a/apps/browser/webpack.config.js +++ b/apps/browser/webpack.config.js @@ -66,8 +66,7 @@ const moduleRules = [ { loader: "babel-loader", options: { - configFile: false, - plugins: ["@angular/compiler-cli/linker/babel"], + configFile: "../../babel.config.json", }, }, ], @@ -166,8 +165,8 @@ const mainConfig = { "content/notificationBar": "./src/autofill/content/notification-bar.ts", "content/contextMenuHandler": "./src/autofill/content/context-menu-handler.ts", "content/content-message-handler": "./src/autofill/content/content-message-handler.ts", - "content/fido2/content-script": "./src/vault/fido2/content/content-script.ts", - "content/fido2/page-script": "./src/vault/fido2/content/page-script.ts", + "content/fido2-content-script": "./src/autofill/fido2/content/fido2-content-script.ts", + "content/fido2-page-script": "./src/autofill/fido2/content/fido2-page-script.ts", "notification/bar": "./src/autofill/notification/bar.ts", "overlay/button": "./src/autofill/overlay/pages/button/bootstrap-autofill-overlay-button.ts", "overlay/list": "./src/autofill/overlay/pages/list/bootstrap-autofill-overlay-list.ts", @@ -275,8 +274,8 @@ if (manifestVersion == 2) { mainConfig.entry.background = "./src/platform/background.ts"; mainConfig.entry["content/lp-suppress-import-download-script-append-mv2"] = "./src/tools/content/lp-suppress-import-download-script-append.mv2.ts"; - mainConfig.entry["content/fido2/page-script-append-mv2"] = - "./src/vault/fido2/content/page-script-append.mv2.ts"; + mainConfig.entry["content/fido2-page-script-append-mv2"] = + "./src/autofill/fido2/content/fido2-page-script-append.mv2.ts"; configs.push(mainConfig); } else { diff --git a/apps/cli/package.json b/apps/cli/package.json index 3da80a0dda4..1ad09cc17a5 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.5.0", + "version": "2024.6.0", "keywords": [ "bitwarden", "password", @@ -80,7 +80,7 @@ "papaparse": "5.4.1", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "tldts": "6.1.20", + "tldts": "6.1.22", "zxcvbn": "4.4.2" } } 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 7546d116092..1bb7a24ce77 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 @@ -9,8 +9,10 @@ export class OrganizationCollectionRequest extends CollectionExport { req.name = "Collection name"; req.externalId = null; req.groups = [SelectionReadOnly.template(), SelectionReadOnly.template()]; + req.users = [SelectionReadOnly.template(), SelectionReadOnly.template()]; return req; } groups: SelectionReadOnly[]; + users: SelectionReadOnly[]; } diff --git a/apps/cli/src/base-program.ts b/apps/cli/src/base-program.ts new file mode 100644 index 00000000000..46aadc323c3 --- /dev/null +++ b/apps/cli/src/base-program.ts @@ -0,0 +1,173 @@ +import * as chalk from "chalk"; +import { firstValueFrom, map } from "rxjs"; + +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +import { UnlockCommand } from "./auth/commands/unlock.command"; +import { Response } from "./models/response"; +import { ListResponse } from "./models/response/list.response"; +import { MessageResponse } from "./models/response/message.response"; +import { StringResponse } from "./models/response/string.response"; +import { TemplateResponse } from "./models/response/template.response"; +import { ServiceContainer } from "./service-container"; +import { CliUtils } from "./utils"; + +const writeLn = CliUtils.writeLn; + +export abstract class BaseProgram { + constructor(protected serviceContainer: ServiceContainer) {} + + protected processResponse(response: Response, exitImmediately = false) { + if (!response.success) { + if (process.env.BW_QUIET !== "true") { + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else { + writeLn(chalk.redBright(response.message), true, true); + } + } + const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; + if (exitImmediately) { + process.exit(exitCode); + } else { + process.exitCode = exitCode; + } + return; + } + + if (process.env.BW_RESPONSE === "true") { + writeLn(this.getJson(response), true, false); + } else if (response.data != null) { + let out: string = null; + + if (response.data.object === "template") { + out = this.getJson((response.data as TemplateResponse).template); + } + + if (out == null) { + if (response.data.object === "string") { + const data = (response.data as StringResponse).data; + if (data != null) { + out = data; + } + } else if (response.data.object === "list") { + out = this.getJson((response.data as ListResponse).data); + } else if (response.data.object === "message") { + out = this.getMessage(response); + } else { + out = this.getJson(response.data); + } + } + + if (out != null && process.env.BW_QUIET !== "true") { + writeLn(out, true, false); + } + } + if (exitImmediately) { + process.exit(0); + } else { + process.exitCode = 0; + } + } + + private getJson(obj: any): string { + if (process.env.BW_PRETTY === "true") { + return JSON.stringify(obj, null, " "); + } else { + return JSON.stringify(obj); + } + } + + protected getMessage(response: Response): string { + const message = response.data as MessageResponse; + if (process.env.BW_RAW === "true") { + return message.raw; + } + + let out = ""; + if (message.title != null) { + if (message.noColor) { + out = message.title; + } else { + out = chalk.greenBright(message.title); + } + } + if (message.message != null) { + if (message.title != null) { + out += "\n"; + } + out += message.message; + } + return out.trim() === "" ? null : out; + } + + protected async exitIfAuthed() { + const authed = await firstValueFrom( + this.serviceContainer.authService.activeAccountStatus$.pipe( + map((status) => status > AuthenticationStatus.LoggedOut), + ), + ); + if (authed) { + const email = await firstValueFrom( + this.serviceContainer.accountService.activeAccount$.pipe(map((a) => a?.email)), + ); + this.processResponse(Response.error("You are already logged in as " + email + "."), true); + } + } + + protected async exitIfNotAuthed() { + const authed = await this.serviceContainer.stateService.getIsAuthenticated(); + if (!authed) { + this.processResponse(Response.error("You are not logged in."), true); + } + } + + protected async exitIfLocked() { + await this.exitIfNotAuthed(); + if (await this.serviceContainer.cryptoService.hasUserKey()) { + return; + } else if (process.env.BW_NOINTERACTION !== "true") { + // must unlock + if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) { + const response = Response.error( + "Your vault is locked. You must unlock your vault using your session key.\n" + + "If you do not have your session key, you can get a new one by logging out and logging in again.", + ); + this.processResponse(response, true); + } else { + const command = new UnlockCommand( + this.serviceContainer.accountService, + this.serviceContainer.masterPasswordService, + this.serviceContainer.cryptoService, + this.serviceContainer.stateService, + this.serviceContainer.cryptoFunctionService, + this.serviceContainer.apiService, + this.serviceContainer.logService, + this.serviceContainer.keyConnectorService, + this.serviceContainer.environmentService, + this.serviceContainer.syncService, + this.serviceContainer.organizationApiService, + this.serviceContainer.logout, + this.serviceContainer.kdfConfigService, + ); + const response = await command.run(null, null); + if (!response.success) { + this.processResponse(response, true); + } + } + } else { + this.processResponse(Response.error("Vault is locked."), true); + } + } + + protected async exitIfFeatureFlagDisabled(featureFlag: FeatureFlag) { + const enabled = await firstValueFrom( + this.serviceContainer.configService.getFeatureFlag$(featureFlag), + ); + + if (!enabled) { + this.processResponse(Response.error("This command is temporarily unavailable."), true); + } + } +} diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index e64ff8b5512..75cd241207c 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -170,10 +170,17 @@ export class EditCommand { : req.groups.map( (g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords, g.manage), ); + const users = + req.users == null + ? null + : req.users.map( + (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), + ); const request = new CollectionRequest(); request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; + request.users = users; const response = await this.apiService.putCollection(req.organizationId, id, request); const view = CollectionExport.toView(req); view.id = response.id; diff --git a/apps/cli/src/commands/serve.command.ts b/apps/cli/src/commands/serve.command.ts index 2b1e21f8abb..8949e5b71e5 100644 --- a/apps/cli/src/commands/serve.command.ts +++ b/apps/cli/src/commands/serve.command.ts @@ -87,6 +87,7 @@ export class ServeCommand { this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.organizationService, ); this.editCommand = new EditCommand( this.serviceContainer.cipherService, diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index 4849aef1512..c480d9d1aff 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -6,6 +6,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ApiService } from "@bitwarden/common/services/api.service"; @@ -21,8 +22,10 @@ export class NodeApiService extends ApiService { platformUtilsService: PlatformUtilsService, environmentService: EnvironmentService, appIdService: AppIdService, + refreshAccessTokenErrorCallback: () => Promise, + logService: LogService, + logoutCallback: () => Promise, vaultTimeoutSettingsService: VaultTimeoutSettingsService, - logoutCallback: (expired: boolean) => Promise, customUserAgent: string = null, ) { super( @@ -30,8 +33,10 @@ export class NodeApiService extends ApiService { platformUtilsService, environmentService, appIdService, - vaultTimeoutSettingsService, + refreshAccessTokenErrorCallback, + logService, logoutCallback, + vaultTimeoutSettingsService, customUserAgent, ); } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 667e0f683fd..597b388a05b 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -1,6 +1,6 @@ import * as chalk from "chalk"; import { program, Command, OptionValues } from "commander"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -8,6 +8,7 @@ import { LockCommand } from "./auth/commands/lock.command"; import { LoginCommand } from "./auth/commands/login.command"; import { LogoutCommand } from "./auth/commands/logout.command"; import { UnlockCommand } from "./auth/commands/unlock.command"; +import { BaseProgram } from "./base-program"; import { CompletionCommand } from "./commands/completion.command"; import { ConfigCommand } from "./commands/config.command"; import { EncodeCommand } from "./commands/encode.command"; @@ -15,20 +16,14 @@ import { ServeCommand } from "./commands/serve.command"; import { StatusCommand } from "./commands/status.command"; import { UpdateCommand } from "./commands/update.command"; import { Response } from "./models/response"; -import { ListResponse } from "./models/response/list.response"; import { MessageResponse } from "./models/response/message.response"; -import { StringResponse } from "./models/response/string.response"; -import { TemplateResponse } from "./models/response/template.response"; -import { ServiceContainer } from "./service-container"; import { GenerateCommand } from "./tools/generate.command"; import { CliUtils } from "./utils"; import { SyncCommand } from "./vault/sync.command"; const writeLn = CliUtils.writeLn; -export class Program { - constructor(protected serviceContainer: ServiceContainer) {} - +export class Program extends BaseProgram { async register() { program .option("--pretty", "Format output. JSON is tabbed with two spaces.") @@ -88,6 +83,11 @@ export class Program { }); program.on("--help", () => { + writeLn( + chalk.yellowBright( + "\n Tip: Managing and retrieving secrets for dev environments is easier with Bitwarden Secrets Manager. Learn more under https://bitwarden.com/products/secrets-manager/", + ), + ); writeLn("\n Examples:"); writeLn(""); writeLn(" bw login"); @@ -517,147 +517,4 @@ export class Program { await command.run(cmd); }); } - - protected processResponse(response: Response, exitImmediately = false) { - if (!response.success) { - if (process.env.BW_QUIET !== "true") { - if (process.env.BW_RESPONSE === "true") { - writeLn(this.getJson(response), true, false); - } else { - writeLn(chalk.redBright(response.message), true, true); - } - } - const exitCode = process.env.BW_CLEANEXIT ? 0 : 1; - if (exitImmediately) { - process.exit(exitCode); - } else { - process.exitCode = exitCode; - } - return; - } - - if (process.env.BW_RESPONSE === "true") { - writeLn(this.getJson(response), true, false); - } else if (response.data != null) { - let out: string = null; - - if (response.data.object === "template") { - out = this.getJson((response.data as TemplateResponse).template); - } - - if (out == null) { - if (response.data.object === "string") { - const data = (response.data as StringResponse).data; - if (data != null) { - out = data; - } - } else if (response.data.object === "list") { - out = this.getJson((response.data as ListResponse).data); - } else if (response.data.object === "message") { - out = this.getMessage(response); - } else { - out = this.getJson(response.data); - } - } - - if (out != null && process.env.BW_QUIET !== "true") { - writeLn(out, true, false); - } - } - if (exitImmediately) { - process.exit(0); - } else { - process.exitCode = 0; - } - } - - private getJson(obj: any): string { - if (process.env.BW_PRETTY === "true") { - return JSON.stringify(obj, null, " "); - } else { - return JSON.stringify(obj); - } - } - - private getMessage(response: Response): string { - const message = response.data as MessageResponse; - if (process.env.BW_RAW === "true") { - return message.raw; - } - - let out = ""; - if (message.title != null) { - if (message.noColor) { - out = message.title; - } else { - out = chalk.greenBright(message.title); - } - } - if (message.message != null) { - if (message.title != null) { - out += "\n"; - } - out += message.message; - } - return out.trim() === "" ? null : out; - } - - private async exitIfAuthed() { - const authed = await firstValueFrom( - this.serviceContainer.authService.activeAccountStatus$.pipe( - map((status) => status > AuthenticationStatus.LoggedOut), - ), - ); - if (authed) { - const email = await firstValueFrom( - this.serviceContainer.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - this.processResponse(Response.error("You are already logged in as " + email + "."), true); - } - } - - private async exitIfNotAuthed() { - const authed = await this.serviceContainer.stateService.getIsAuthenticated(); - if (!authed) { - this.processResponse(Response.error("You are not logged in."), true); - } - } - - protected async exitIfLocked() { - await this.exitIfNotAuthed(); - if (await this.serviceContainer.cryptoService.hasUserKey()) { - return; - } else if (process.env.BW_NOINTERACTION !== "true") { - // must unlock - if (await this.serviceContainer.keyConnectorService.getUsesKeyConnector()) { - const response = Response.error( - "Your vault is locked. You must unlock your vault using your session key.\n" + - "If you do not have your session key, you can get a new one by logging out and logging in again.", - ); - this.processResponse(response, true); - } else { - const command = new UnlockCommand( - this.serviceContainer.accountService, - this.serviceContainer.masterPasswordService, - this.serviceContainer.cryptoService, - this.serviceContainer.stateService, - this.serviceContainer.cryptoFunctionService, - this.serviceContainer.apiService, - this.serviceContainer.logService, - this.serviceContainer.keyConnectorService, - this.serviceContainer.environmentService, - this.serviceContainer.syncService, - this.serviceContainer.organizationApiService, - this.serviceContainer.logout, - this.serviceContainer.kdfConfigService, - ); - const response = await command.run(null, null); - if (!response.success) { - this.processResponse(response, true); - } - } - } else { - this.processResponse(Response.error("Vault is locked."), true); - } - } } diff --git a/apps/cli/src/register-oss-programs.ts b/apps/cli/src/register-oss-programs.ts index f47aa528543..d8aa54118d7 100644 --- a/apps/cli/src/register-oss-programs.ts +++ b/apps/cli/src/register-oss-programs.ts @@ -15,8 +15,8 @@ export async function registerOssPrograms(serviceContainer: ServiceContainer) { await program.register(); const vaultProgram = new VaultProgram(serviceContainer); - await vaultProgram.register(); + vaultProgram.register(); const sendProgram = new SendProgram(serviceContainer); - await sendProgram.register(); + sendProgram.register(); } diff --git a/apps/cli/src/service-container.ts b/apps/cli/src/service-container.ts index 882791ef9c9..ff4eb52b84e 100644 --- a/apps/cli/src/service-container.ts +++ b/apps/cli/src/service-container.ts @@ -97,6 +97,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; import { MemoryStorageService as MemoryStorageServiceForStateProviders } from "@bitwarden/common/platform/state/storage/memory-storage.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for service construction +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { AuditService } from "@bitwarden/common/services/audit.service"; import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; @@ -120,8 +123,6 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { ImportApiService, @@ -216,7 +217,6 @@ export class ServiceContainer { folderApiService: FolderApiService; userVerificationApiService: UserVerificationApiService; organizationApiService: OrganizationApiServiceAbstraction; - syncNotifierService: SyncNotifierService; sendApiService: SendApiService; devicesApiService: DevicesApiServiceAbstraction; deviceTrustService: DeviceTrustServiceAbstraction; @@ -255,6 +255,8 @@ export class ServiceContainer { p = path.join(process.env.HOME, ".config/Bitwarden CLI"); } + const logoutCallback = async () => await this.logout(); + this.platformUtilsService = new CliPlatformUtilsService(ClientType.Cli, packageJson); this.logService = new ConsoleLogService( this.platformUtilsService.isDev(), @@ -337,6 +339,7 @@ export class ServiceContainer { this.keyGenerationService, this.encryptService, this.logService, + logoutCallback, ); const migrationRunner = new MigrationRunner( @@ -421,18 +424,22 @@ export class ServiceContainer { VaultTimeoutStringType.Never, // default vault timeout ); + const refreshAccessTokenErrorCallback = () => { + throw new Error("Refresh Access token error"); + }; + this.apiService = new NodeApiService( this.tokenService, this.platformUtilsService, this.environmentService, this.appIdService, + refreshAccessTokenErrorCallback, + this.logService, + logoutCallback, this.vaultTimeoutSettingsService, - async (expired: boolean) => await this.logout(), customUserAgent, ); - this.syncNotifierService = new SyncNotifierService(); - this.organizationApiService = new OrganizationApiService(this.apiService, this.syncService); this.containerService = new ContainerService(this.cryptoService, this.encryptService); @@ -485,7 +492,7 @@ export class ServiceContainer { this.logService, this.organizationService, this.keyGenerationService, - async (expired: boolean) => await this.logout(), + logoutCallback, this.stateProvider, ); @@ -639,7 +646,7 @@ export class ServiceContainer { this.avatarService = new AvatarService(this.apiService, this.stateProvider); - this.syncService = new SyncService( + this.syncService = new DefaultSyncService( this.masterPasswordService, this.accountService, this.apiService, @@ -660,7 +667,7 @@ export class ServiceContainer { this.sendApiService, this.userDecryptionOptionsService, this.avatarService, - async (expired: boolean) => await this.logout(), + logoutCallback, this.billingAccountProfileStateService, this.tokenService, this.authService, diff --git a/apps/cli/src/tools/send/send.program.ts b/apps/cli/src/tools/send/send.program.ts index 86edd28f098..670683e7a2a 100644 --- a/apps/cli/src/tools/send/send.program.ts +++ b/apps/cli/src/tools/send/send.program.ts @@ -7,9 +7,9 @@ import { program, Command, OptionValues } from "commander"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; +import { BaseProgram } from "../../base-program"; import { GetCommand } from "../../commands/get.command"; import { Response } from "../../models/response"; -import { Program } from "../../program"; import { CliUtils } from "../../utils"; import { @@ -27,8 +27,8 @@ import { SendResponse } from "./models/send.response"; const writeLn = CliUtils.writeLn; -export class SendProgram extends Program { - async register() { +export class SendProgram extends BaseProgram { + register() { program.addCommand(this.sendCommand()); // receive is accessible both at `bw receive` and `bw send receive` program.addCommand(this.receiveCommand()); diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 52857ed5424..04ca47ac1e1 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -2,12 +2,12 @@ import { program, Command } from "commander"; import { ConfirmCommand } from "./admin-console/commands/confirm.command"; import { ShareCommand } from "./admin-console/commands/share.command"; +import { BaseProgram } from "./base-program"; import { EditCommand } from "./commands/edit.command"; import { GetCommand } from "./commands/get.command"; import { ListCommand } from "./commands/list.command"; import { RestoreCommand } from "./commands/restore.command"; import { Response } from "./models/response"; -import { Program } from "./program"; import { ExportCommand } from "./tools/export.command"; import { ImportCommand } from "./tools/import.command"; import { CliUtils } from "./utils"; @@ -16,8 +16,8 @@ import { DeleteCommand } from "./vault/delete.command"; const writeLn = CliUtils.writeLn; -export class VaultProgram extends Program { - async register() { +export class VaultProgram extends BaseProgram { + register() { program .addCommand(this.listCommand()) .addCommand(this.getCommand()) @@ -226,6 +226,7 @@ export class VaultProgram extends Program { this.serviceContainer.apiService, this.serviceContainer.folderApiService, this.serviceContainer.billingAccountProfileStateService, + this.serviceContainer.organizationService, ); const response = await command.run(object, encodedJson, cmd); this.processResponse(response); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 78ee04e73c0..716c2b42bb1 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -4,6 +4,7 @@ import * as path from "path"; import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; @@ -32,6 +33,7 @@ export class CreateCommand { private apiService: ApiService, private folderApiService: FolderApiServiceAbstraction, private accountProfileService: BillingAccountProfileStateService, + private organizationService: OrganizationService, ) {} async run( @@ -183,6 +185,8 @@ export class CreateCommand { if (orgKey == null) { throw new Error("No encryption key for this organization."); } + const organization = await this.organizationService.get(req.organizationId); + const currentOrgUserId = organization.organizationUserId; const groups = req.groups == null @@ -190,10 +194,17 @@ export class CreateCommand { : req.groups.map( (g) => new SelectionReadOnlyRequest(g.id, g.readOnly, g.hidePasswords, g.manage), ); + const users = + req.users == null + ? [new SelectionReadOnlyRequest(currentOrgUserId, false, false, true)] + : req.users.map( + (u) => new SelectionReadOnlyRequest(u.id, u.readOnly, u.hidePasswords, u.manage), + ); const request = new CollectionRequest(); request.name = (await this.cryptoService.encrypt(req.name, orgKey)).encryptedString; request.externalId = req.externalId; request.groups = groups; + request.users = users; const response = await this.apiService.postCollection(req.organizationId, request); const view = CollectionExport.toView(req); view.id = response.id; diff --git a/apps/cli/src/vault/sync.command.ts b/apps/cli/src/vault/sync.command.ts index 073b9b5df48..c3c6f637538 100644 --- a/apps/cli/src/vault/sync.command.ts +++ b/apps/cli/src/vault/sync.command.ts @@ -1,4 +1,4 @@ -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { Response } from "../models/response"; import { MessageResponse } from "../models/response/message.response"; diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index b921cab37b2..8617553b377 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arboard" @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 4b2bc2e905e..cded3d57ef6 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -14,9 +14,9 @@ manual_test = [] [dependencies] aes = "=0.8.4" -anyhow = "=1.0.80" +anyhow = "=1.0.86" arboard = { version = "=3.3.2", default-features = false, features = ["wayland-data-control"] } -base64 = "=0.22.0" +base64 = "=0.22.1" cbc = { version = "=0.1.2", features = ["alloc"] } napi = { version = "=2.16.0", features = ["async"] } napi-derive = "=2.16.0" diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 960d56b0362..39c62998a67 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -24,7 +24,7 @@ "**/node_modules/argon2/package.json", "**/node_modules/argon2/lib/binding/napi-v3/argon2.node" ], - "electronVersion": "28.3.1", + "electronVersion": "29.4.2", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", @@ -127,7 +127,7 @@ "entitlementsLoginHelper": "resources/entitlements.mas.loginhelper.plist", "hardenedRuntime": false, "extendInfo": { - "LSMinimumSystemVersion": "10.15.0", + "LSMinimumSystemVersion": "12", "ElectronTeamID": "LTZ2PFU5D6" } }, diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 747d8ec9811..ac12731398b 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,8 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@tsconfig/node16": "1.0.4", - "@types/node": "18.19.29", + "@types/node": "20.14.1", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -99,9 +98,10 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" }, "node_modules/@types/node": { - "version": "18.19.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.29.tgz", - "integrity": "sha512-5pAX7ggTmWZdhUrhRWLPf+5oM7F80bcKVCBbr0zwEkTNzTJL2CWQjznpFgHYy6GrzkYi2Yjy7DHKoynFxqPV8g==", + "version": "20.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.1.tgz", + "integrity": "sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==", + "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 72b2587a4ae..0f92d5b0b3d 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,8 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@tsconfig/node16": "1.0.4", - "@types/node": "18.19.29", + "@types/node": "20.14.1", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 90d9841a618..129e9c43f09 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.5.0", + "version": "2024.6.0", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 6984e2da890..6b6a18958f8 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -278,7 +278,7 @@ export class SettingsComponent implements OnInit { approveLoginRequests: (await this.authRequestService.getAcceptAuthRequests(this.currentUserId)) ?? false, clearClipboard: await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$), - minimizeOnCopyToClipboard: await this.stateService.getMinimizeOnCopyToClipboard(), + minimizeOnCopyToClipboard: await firstValueFrom(this.desktopSettingsService.minimizeOnCopy$), enableFavicons: await firstValueFrom(this.domainSettingsService.showFavicons$), enableTray: await firstValueFrom(this.desktopSettingsService.trayEnabled$), enableMinToTray: await firstValueFrom(this.desktopSettingsService.minimizeToTray$), @@ -286,9 +286,12 @@ export class SettingsComponent implements OnInit { startToTray: await firstValueFrom(this.desktopSettingsService.startToTray$), openAtLogin: await firstValueFrom(this.desktopSettingsService.openAtLogin$), alwaysShowDock: await firstValueFrom(this.desktopSettingsService.alwaysShowDock$), - enableBrowserIntegration: await this.stateService.getEnableBrowserIntegration(), - enableBrowserIntegrationFingerprint: - await this.stateService.getEnableBrowserIntegrationFingerprint(), + enableBrowserIntegration: await firstValueFrom( + this.desktopSettingsService.browserIntegrationEnabled$, + ), + enableBrowserIntegrationFingerprint: await firstValueFrom( + this.desktopSettingsService.browserIntegrationFingerprintEnabled$, + ), enableDuckDuckGoBrowserIntegration: await firstValueFrom( this.desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$, ), @@ -598,7 +601,10 @@ export class SettingsComponent implements OnInit { } async saveMinOnCopyToClipboard() { - await this.stateService.setMinimizeOnCopyToClipboard(this.form.value.minimizeOnCopyToClipboard); + await this.desktopSettingsService.setMinimizeOnCopy( + this.form.value.minimizeOnCopyToClipboard, + this.currentUserId, + ); } async saveClearClipboard() { @@ -656,7 +662,9 @@ export class SettingsComponent implements OnInit { return; } - await this.stateService.setEnableBrowserIntegration(this.form.value.enableBrowserIntegration); + await this.desktopSettingsService.setBrowserIntegrationEnabled( + this.form.value.enableBrowserIntegration, + ); const errorResult = await this.nativeMessagingManifestService.generate( this.form.value.enableBrowserIntegration, @@ -703,7 +711,7 @@ export class SettingsComponent implements OnInit { } async saveBrowserIntegrationFingerprint() { - await this.stateService.setEnableBrowserIntegrationFingerprint( + await this.desktopSettingsService.setBrowserIntegrationFingerprintEnabled( this.form.value.enableBrowserIntegrationFingerprint, ); } diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 4bc34f9af97..e4fdd17dc15 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -1,3 +1,4 @@ +import { DialogRef } from "@angular/cdk/dialog"; import { Component, NgZone, @@ -13,6 +14,7 @@ import { filter, firstValueFrom, map, Subject, takeUntil, timeout } from "rxjs"; import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { FingerprintDialogComponent } from "@bitwarden/auth/angular"; +import { LogoutReason } from "@bitwarden/auth/common"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; @@ -40,15 +42,15 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UserId } from "@bitwarden/common/types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService, ToastOptions, ToastService } from "@bitwarden/components"; import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginApprovalComponent } from "../auth/login/login-approval.component"; @@ -57,7 +59,7 @@ import { PremiumComponent } from "../vault/app/accounts/premium.component"; import { FolderAddEditComponent } from "../vault/app/vault/folder-add-edit.component"; import { SettingsComponent } from "./accounts/settings.component"; -import { ExportComponent } from "./tools/export/export.component"; +import { ExportDesktopComponent } from "./tools/export/export-desktop.component"; import { GeneratorComponent } from "./tools/generator.component"; import { ImportDesktopComponent } from "./tools/import/import-desktop.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; @@ -108,6 +110,7 @@ export class AppComponent implements OnInit, OnDestroy { private idleTimer: number = null; private isIdle = false; private activeUserId: UserId = null; + private activeSimpleDialog: DialogRef = null; private destroy$ = new Subject(); @@ -207,7 +210,7 @@ export class AppComponent implements OnInit, OnDestroy { break; case "logout": this.loading = message.userId == null || message.userId === this.activeUserId; - await this.logOut(!!message.expired, message.userId); + await this.logOut(message.logoutReason, message.userId); this.loading = false; break; case "lockVault": @@ -366,7 +369,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.dialogService.open(ImportDesktopComponent); break; case "exportVault": - await this.openExportVault(); + await this.dialogService.open(ExportDesktopComponent); break; case "newLogin": this.routeToVault("add", CipherType.Login); @@ -463,26 +466,6 @@ export class AppComponent implements OnInit, OnDestroy { this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); } - async openExportVault() { - this.modalService.closeAll(); - - const [modal, childComponent] = await this.modalService.openViewRef( - ExportComponent, - this.exportVaultModalRef, - ); - this.modal = modal; - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onSaved.subscribe(() => { - this.modal.close(); - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - this.modal.onClosed.subscribe(() => { - this.modal = null; - }); - } - async addFolder() { this.modalService.closeAll(); @@ -565,9 +548,73 @@ export class AppComponent implements OnInit, OnDestroy { this.messagingService.send("updateAppMenu", { updateRequest: updateRequest }); } + private async displayLogoutReason(logoutReason: LogoutReason) { + let toastOptions: ToastOptions; + + switch (logoutReason) { + case "invalidSecurityStamp": + case "sessionExpired": { + toastOptions = { + variant: "warning", + title: this.i18nService.t("loggedOut"), + message: this.i18nService.t("loginExpired"), + }; + break; + } + // We don't expect these scenarios to be common, but we want the user to + // understand why they are being logged out before a process reload. + case "accessTokenUnableToBeDecrypted": { + // Don't create multiple dialogs if this fires multiple times + if (this.activeSimpleDialog) { + // Let the caller of this function listen for the dialog to close + return firstValueFrom(this.activeSimpleDialog.closed); + } + + this.activeSimpleDialog = this.dialogService.openSimpleDialogRef({ + title: { key: "loggedOut" }, + content: { key: "accessTokenUnableToBeDecrypted" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + + await firstValueFrom(this.activeSimpleDialog.closed); + this.activeSimpleDialog = null; + + break; + } + case "refreshTokenSecureStorageRetrievalFailure": { + // Don't create multiple dialogs if this fires multiple times + if (this.activeSimpleDialog) { + // Let the caller of this function listen for the dialog to close + return firstValueFrom(this.activeSimpleDialog.closed); + } + + this.activeSimpleDialog = this.dialogService.openSimpleDialogRef({ + title: { key: "loggedOut" }, + content: { key: "refreshTokenSecureStorageRetrievalFailure" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "danger", + }); + + await firstValueFrom(this.activeSimpleDialog.closed); + this.activeSimpleDialog = null; + + break; + } + } + + if (toastOptions) { + this.toastService.showToast(toastOptions); + } + } + // Even though the userId parameter is no longer optional doesn't mean a message couldn't be // passing null-ish values to us. - private async logOut(expired: boolean, userId: UserId) { + private async logOut(logoutReason: LogoutReason, userId: UserId) { + await this.displayLogoutReason(logoutReason); + const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); @@ -640,15 +687,7 @@ export class AppComponent implements OnInit, OnDestroy { // This must come last otherwise the logout will prematurely trigger // a process reload before all the state service user data can be cleaned up if (userBeingLoggedOut === activeUserId) { - this.authService.logOut(async () => { - if (expired) { - this.platformUtilsService.showToast( - "warning", - this.i18nService.t("loggedOut"), - this.i18nService.t("loginExpired"), - ); - } - }); + this.authService.logOut(async () => {}); } } @@ -730,7 +769,7 @@ export class AppComponent implements OnInit, OnDestroy { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises options[1] === "logOut" - ? this.logOut(false, userId as UserId) + ? this.logOut("vaultTimeout", userId as UserId) : await this.vaultTimeoutService.lock(userId); } } diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index de228d0399f..c4861b96608 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -8,7 +8,6 @@ import { NgModule } from "@angular/core"; import { ColorPasswordCountPipe } from "@bitwarden/angular/pipes/color-password-count.pipe"; import { ColorPasswordPipe } from "@bitwarden/angular/pipes/color-password.pipe"; import { DialogModule, CalloutModule } from "@bitwarden/components"; -import { ExportScopeCalloutComponent } from "@bitwarden/vault-export-ui"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { DeleteAccountComponent } from "../auth/delete-account.component"; @@ -47,7 +46,6 @@ import { HeaderComponent } from "./layout/header.component"; import { NavComponent } from "./layout/nav.component"; import { SearchComponent } from "./layout/search/search.component"; import { SharedModule } from "./shared/shared.module"; -import { ExportComponent } from "./tools/export/export.component"; import { GeneratorComponent } from "./tools/generator.component"; import { PasswordGeneratorHistoryComponent } from "./tools/password-generator-history.component"; import { AddEditComponent as SendAddEditComponent } from "./tools/send/add-edit.component"; @@ -63,7 +61,6 @@ import { SendComponent } from "./tools/send/send.component"; CalloutModule, DeleteAccountComponent, UserVerificationComponent, - ExportScopeCalloutComponent, ], declarations: [ AccessibilityCookieComponent, @@ -77,7 +74,6 @@ import { SendComponent } from "./tools/send/send.component"; ColorPasswordPipe, ColorPasswordCountPipe, EnvironmentComponent, - ExportComponent, FolderAddEditComponent, HeaderComponent, HintComponent, diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 0452e9be837..8793587300f 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -15,10 +15,10 @@ import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwar import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; import { ContainerService } from "@bitwarden/common/platform/services/container.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; +import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/platform/sync"; import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service"; import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service"; import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; import { NativeMessagingService } from "../../services/native-messaging.service"; diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 8d800970535..25d4df5f935 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -151,7 +151,7 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: MessageSender, - useFactory: (subject: Subject>) => + useFactory: (subject: Subject>>) => MessageSender.combine( new ElectronRendererMessageSender(), // Communication with main process new SubjectMessageSender(subject), // Communication with ourself @@ -160,7 +160,7 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: MessageListener, - useFactory: (subject: Subject>) => + useFactory: (subject: Subject>>) => new MessageListener( merge( subject.asObservable(), // For messages from the same context diff --git a/apps/desktop/src/app/tools/export/export-desktop.component.html b/apps/desktop/src/app/tools/export/export-desktop.component.html new file mode 100644 index 00000000000..9aa59c5a636 --- /dev/null +++ b/apps/desktop/src/app/tools/export/export-desktop.component.html @@ -0,0 +1,26 @@ + + {{ "exportVault" | i18n }} + + + + + + + + diff --git a/apps/desktop/src/app/tools/export/export-desktop.component.ts b/apps/desktop/src/app/tools/export/export-desktop.component.ts new file mode 100644 index 00000000000..352fa29910a --- /dev/null +++ b/apps/desktop/src/app/tools/export/export-desktop.component.ts @@ -0,0 +1,33 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import { ExportComponent } from "@bitwarden/vault-export-ui"; + +@Component({ + templateUrl: "export-desktop.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + DialogModule, + AsyncActionsModule, + ButtonModule, + ExportComponent, + ], +}) +export class ExportDesktopComponent { + protected disabled = false; + protected loading = false; + + constructor(public dialogRef: DialogRef) {} + + /** + * Callback that is called after a successful export. + */ + protected async onSuccessfulExport(organizationId: string): Promise { + this.dialogRef.close(); + } +} diff --git a/apps/desktop/src/app/tools/export/export.component.html b/apps/desktop/src/app/tools/export/export.component.html deleted file mode 100644 index 3792713e61e..00000000000 --- a/apps/desktop/src/app/tools/export/export.component.html +++ /dev/null @@ -1,40 +0,0 @@ - diff --git a/apps/desktop/src/app/tools/export/export.component.ts b/apps/desktop/src/app/tools/export/export.component.ts deleted file mode 100644 index 6cf5760a1cb..00000000000 --- a/apps/desktop/src/app/tools/export/export.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { UntypedFormBuilder } from "@angular/forms"; - -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 { 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 { DialogService } from "@bitwarden/components"; -import { VaultExportServiceAbstraction } from "@bitwarden/vault-export-core"; -import { ExportComponent as BaseExportComponent } from "@bitwarden/vault-export-ui"; - -@Component({ - selector: "app-export", - templateUrl: "export.component.html", -}) -export class ExportComponent extends BaseExportComponent implements OnInit { - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - exportService: VaultExportServiceAbstraction, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - formBuilder: UntypedFormBuilder, - logService: LogService, - fileDownloadService: FileDownloadService, - dialogService: DialogService, - organizationService: OrganizationService, - ) { - super( - i18nService, - platformUtilsService, - exportService, - eventCollectionService, - policyService, - logService, - formBuilder, - fileDownloadService, - dialogService, - organizationService, - ); - } -} diff --git a/apps/desktop/src/auth/lock.component.spec.ts b/apps/desktop/src/auth/lock.component.spec.ts index 434d94bf5bd..c46b791b1b6 100644 --- a/apps/desktop/src/auth/lock.component.spec.ts +++ b/apps/desktop/src/auth/lock.component.spec.ts @@ -32,6 +32,7 @@ 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 } from "@bitwarden/components"; import { LockComponent } from "./lock.component"; @@ -174,6 +175,10 @@ describe("LockComponent", () => { provide: KdfConfigService, useValue: mock(), }, + { + provide: SyncService, + useValue: mock(), + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts index 1b18f9f75f6..ad9f6e5d01e 100644 --- a/apps/desktop/src/auth/lock.component.ts +++ b/apps/desktop/src/auth/lock.component.ts @@ -26,6 +26,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService } from "@bitwarden/components"; const BroadcasterSubscriptionId = "LockComponent"; @@ -67,6 +68,7 @@ export class LockComponent extends BaseLockComponent { accountService: AccountService, authService: AuthService, kdfConfigService: KdfConfigService, + syncService: SyncService, ) { super( masterPasswordService, @@ -93,6 +95,7 @@ export class LockComponent extends BaseLockComponent { accountService, authService, kdfConfigService, + syncService, ); } diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 1601a2ba8fd..1c835869d63 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Spesifiseer die basisbronadres van u selfgehuisveste Bitwarden-installasie." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Pasgemaakte omgewing" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Uitgeteken" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "U aantekensessie het verstryk." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Wagwoord bygewerk", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Stuur Kluis Uit" }, "fileFormat": { "message": "Lêerformaat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha-bronadres", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Verander rekening" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Opsies" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Vergrendel" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Aan teken versoek" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Ontblote Hoofwagwoord" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Belangrik:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Jou hoofwagwoord kan nie herkry word as jy dit vergeet nie!" }, diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index f94b869503f..4f71977603a 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "حدد عنوان URL الأساسي لتثبيت Bitwarden المستضاف محليًا." }, + "selfHostedBaseUrlHint": { + "message": "حدد عنوان URL الأساسي لمحالّك التي استضافت تثبيت Bitwarden على سبيل المثال: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "للتكوين المتقدم، يمكنك تحديد عنوان URL الأساسي لكل خدمة بشكل مستقل." + }, + "selfHostedEnvFormInvalid": { + "message": "يجب عليك إضافة رابط الخادم الأساسي أو على الأقل بيئة مخصصة." + }, "customEnvironment": { "message": "بيئة مخصصة" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "الخروج" }, + "loggedOutDesc": { + "message": "لقد خرجتَ من حسابك." + }, "loginExpired": { "message": "انتهت صلاحية جلسة الدخول." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "خطأ في تحديث رمز الوصول" + }, + "errorRefreshingAccessTokenDesc": { + "message": "لم يُعثر على رمز تحديث أو مفاتيح API. الرجاء محاولة تسجيل الخروج وتسجيل الدخول مرة أخرى." + }, "help": { "message": "المساعدة" }, @@ -1299,12 +1317,42 @@ "message": "تم تحديث كلمة المرور", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "تصدير من" + }, "exportVault": { "message": "تصدير الخزنة" }, "fileFormat": { "message": "صيغة الملف" }, + "fileEncryptedExportWarningDesc": { + "message": "سيكون تصدير الملف هذا محميًا بكلمة مرور وسيتطلب كلمة مرور الملف لفك تشفيره." + }, + "filePassword": { + "message": "كلمة مرور الملف" + }, + "exportPasswordDescription": { + "message": "ستُستخدم كلمة المرور هذه لتصدير واستيراد هذا الملف" + }, + "accountRestrictedOptionDescription": { + "message": "استخدم مفتاح تشفير حسابك، مشتقة من اسم المستخدم في حسابك وكلمة المرور الرئيسية، لتشفير التصدير وتقييد الاستيراد إلى حساب Bitwarden الحالي فقط." + }, + "passwordProtected": { + "message": "كلمة المرور محمية" + }, + "passwordProtectedOptionDescription": { + "message": "تعيين كلمة مرور للملف لتشفير التصدير واستيراده إلى أي حساب Bitwarden باستخدام كلمة المرور لفك التشفير." + }, + "exportTypeHeading": { + "message": "نوع التصدير" + }, + "accountRestricted": { + "message": "الحساب مقيد" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"كلمة مرور الملف\" و \"تأكيد كلمة مرور الملف\" غير متطابقين." + }, "hCaptchaUrl": { "message": "رابط hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "تبديل الحساب" }, + "alreadyHaveAccount": { + "message": "لديك حساب بالفعل؟" + }, "options": { "message": "الخيارات" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "تصدير خزنة المؤسسة" + }, + "exportingOrganizationVaultDesc": { + "message": "فقط مستودع المؤسسة المرتبط بـ $ORGANIZATION$ سيتم تصديره. لن يتم إدراج العناصر في المستودعات الفردية أو المنظمات الأخرى.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "مقفل" }, @@ -2133,7 +2196,7 @@ "message": "إنشاء بريد إلكتروني مستعار مع خدمة إعادة توجيه خارجية." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ خطأ: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2147,11 +2210,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": { @@ -2161,7 +2224,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "رمز API غير صالح $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2171,7 +2234,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "رمز API غير صالح $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2185,7 +2248,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": { @@ -2195,7 +2258,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": { @@ -2205,7 +2268,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "رابط $SERVICENAME$ غير صالح.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2215,7 +2278,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "حدث خطأ $SERVICENAME$ غير معروف.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2225,7 +2288,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "وكيل مجهول: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "طلب تسجيل الدخول" }, + "creatingAccountOn": { + "message": "إنشاء الحساب في" + }, + "checkYourEmail": { + "message": "تحقق من بريدك الإلكتروني" + }, + "followTheLinkInTheEmailSentTo": { + "message": "اتبع الرابط الموجود في البريد الإلكتروني المرسل إلى" + }, + "andContinueCreatingYourAccount": { + "message": "واستمر في إنشاء حسابك." + }, + "noEmail": { + "message": "لا يوجد بريد إلكتروني؟" + }, + "goBack": { + "message": "الرجوع إلى الخلف" + }, + "toEditYourEmailAddress": { + "message": "لتحرير عنوان بريدك الإلكتروني." + }, "exposedMasterPassword": { "message": "كلمة المرور الرئيسية مكشوفة" }, @@ -2408,6 +2492,12 @@ "important": { "message": "مهم:" }, + "accessTokenUnableToBeDecrypted": { + "message": "لقد قمت بتسجيل الخروج لأنه لا يمكن فك تشفير الرمز المميز الخاص بك. الرجاء تسجيل الدخول مرة أخرى لحل هذه المشكلة." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "لقد قمت بتسجيل الخروج لأنه تعذر استرداد رمز التحديث الخاص بك. الرجاء تسجيل الدخول مرة أخرى لحل هذه المشكلة." + }, "masterPasswordHint": { "message": "لا يمكن استعادة كلمة المرور الرئيسية إذا نسيتها!" }, @@ -2824,7 +2914,7 @@ "message": "خطأ في تعيين مجلد الهدف." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "عرض العناصر في $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "العودة إلى $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index bfeacdeafd9..dc2c5b28f8a 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Öz-özünə sahiblik edən Bitwarden quraşdırmasının baza URL-sini müəyyənləşdirin." }, + "selfHostedBaseUrlHint": { + "message": "Şirkət daxili sahiblik edən Bitwarden quraşdırmasının təməl URL-sini qeyd edin. Nümunə: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Qabaqcıl konfiqurasiya üçün hər xidmətin təməl URL-sini müstəqil olaraq qeyd edə bilərsiniz." + }, + "selfHostedEnvFormInvalid": { + "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." + }, "customEnvironment": { "message": "Özəl mühit" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Çıxış edildi" }, + "loggedOutDesc": { + "message": "Hesabınızdan çıxış etmisiniz." + }, "loginExpired": { "message": "Seansın müddəti bitdi." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Müraciət tokeni təzələmə xətası" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Təzələmə tokeni və ya API açarlar tapılmadı. Lütfən çıxış edib yenidən giriş etməyə çalışın." + }, "help": { "message": "Kömək" }, @@ -1299,12 +1317,42 @@ "message": "Parol güncəlləndi", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Buradan xaricə köçür" + }, "exportVault": { "message": "Anbarı xaricə köçür" }, "fileFormat": { "message": "Fayl formatı" }, + "fileEncryptedExportWarningDesc": { + "message": "Bu faylın xaricə köçürülməsi, parolla qorunacaq və şifrəsini açmaq üçün fayl parolu tələb olunacaq." + }, + "filePassword": { + "message": "Fayl parolu" + }, + "exportPasswordDescription": { + "message": "Bu parol, bu faylı daxilə və xaricə köçürmək üçün istifadə olunacaq" + }, + "accountRestrictedOptionDescription": { + "message": "Xaricə köçürməni şifrələmək və daxilə köçürməni yalnız mövcud Bitwarden hesabı ilə məhdudlaşdırmaq üçün hesabınızın istifadəçi adı və Ana Parolundan əldə edilən hesab şifrələmə açarınızı istifadə edin." + }, + "passwordProtected": { + "message": "Parolla qorunan" + }, + "passwordProtectedOptionDescription": { + "message": "Xaricə köçürməni şifrələmək üçün bir fayl parolu təyin edin və şifrəni açma parolunu istifadə edərək bunu istənilən Bitwarden hesabına köçürün." + }, + "exportTypeHeading": { + "message": "Xaricə köçürmə növü" + }, + "accountRestricted": { + "message": "Hesab məhdudlaşdırıldı" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Fayl parolu\" və \"Fayl parolunu təsdiqlə\" uyuşmur." + }, "hCaptchaUrl": { "message": "hCaptcha ünvanı", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Hesabı dəyişdir" }, + "alreadyHaveAccount": { + "message": "Artıq hesabınız var?" + }, "options": { "message": "Seçimlər" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Təşkilat anbarını xaricə köçürmə" + }, + "exportingOrganizationVaultDesc": { + "message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat anbarı ixrac ediləcək. Fərdi anbardakı və digər təşkilat elementlər daxil edilmir.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Kilidli" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Giriş tələb olundu" }, + "creatingAccountOn": { + "message": "Hesab yaradılır" + }, + "checkYourEmail": { + "message": "E-poçtunuzu yoxlayın" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Göndərilən e-poçtdakı keçidi izləyin" + }, + "andContinueCreatingYourAccount": { + "message": "və hesabınızı yaratmağa davam edin." + }, + "noEmail": { + "message": "E-poçt yoxdur?" + }, + "goBack": { + "message": "Geri qayıt" + }, + "toEditYourEmailAddress": { + "message": "və e-poçt ünvanına düzəliş et." + }, "exposedMasterPassword": { "message": "İfşa olunmuş ana parol" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Vacib:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Müraciət tokeninizin şifrəsi açıla bilmədiyi üçün çıxış etdiniz. Bu problemi həll etmək üçün lütfən yenidən giriş edin." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Təzələmə tokeniniz alına bilmədiyi üçün çıxış etdiniz. Bu problemi həll etmək üçün lütfən yenidən giriş edin." + }, "masterPasswordHint": { "message": "Unutsanız, ana parolunuz bərpa edilə bilməz!" }, @@ -2824,7 +2914,7 @@ "message": "Hədəf qovluğa təyin etmə xətası." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ daxilindəki elementlərə bax", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Geri qayıt: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Geri", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Sil: $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 4ece988a001..73bd046a82d 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Увядзіце асноўны URL-адрас вашага лакальнага размяшчэння ўсталяванага Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Вы выйшлі" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Даведка" }, @@ -1299,12 +1317,42 @@ "message": "Пароль абноўлены", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Экспартаваць сховішча" }, "fileFormat": { "message": "Фармат файла" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "URL-адрас hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Пераключыць уліковы запіс" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Параметры" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Заблакіравана" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Запытаны ўваход" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Скампраметаваны асноўны пароль" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Важна:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Ваш асноўны пароль немагчыма будзе аднавіць, калі вы яго забудзеце!" }, diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 1c739acde18..425380fdcea 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Укажете базовия адрес за собствената ви инсталирана среда на Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Посочете базовия адрес на Вашата собствена инсталация на Битуорден. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За по-детайлна настройка, може да укажете основния адрес на всяка услуга поотделно." + }, + "selfHostedEnvFormInvalid": { + "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." + }, "customEnvironment": { "message": "Специална среда" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Бяхте отписани" }, + "loggedOutDesc": { + "message": "Бяхте отписан(а) от регистрацията си." + }, "loginExpired": { "message": "Сесията ви изтече." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Грешка при опресняването на идентификатора за достъп" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Няма намерен идентификатор за опресняване или ключове за ППИ. Опитайте да се отпишете и да се впишете отново." + }, "help": { "message": "Помощ" }, @@ -1299,12 +1317,42 @@ "message": "Обновена парола", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Изнасяне от" + }, "exportVault": { "message": "Изнасяне на трезора" }, "fileFormat": { "message": "Формат на файла" }, + "fileEncryptedExportWarningDesc": { + "message": "Изнесеният файл ще бъде защитен с парола, която ще бъде необходима за дешифриране на файла." + }, + "filePassword": { + "message": "Парола на файла" + }, + "exportPasswordDescription": { + "message": "Парола ще се използва при изнасянето и при внасянето на този файл" + }, + "accountRestrictedOptionDescription": { + "message": "Използвайте ключа си за шифриране, който се получава чрез комбиниране на потребителското име на регистрацията Ви и главната парола. С него изнасянето ще се шифрира и внасянето ще бъда възможно само в текущата регистрация в Битуорден." + }, + "passwordProtected": { + "message": "Защита с парола" + }, + "passwordProtectedOptionDescription": { + "message": "Задайте парола за файла, за да шифровате изнесените данни. Ще можете да внесете данните във всяка регистрация в Битуорден използвайки паролата за дешифриране." + }, + "exportTypeHeading": { + "message": "Вид изнасяне" + }, + "accountRestricted": { + "message": "Регистрацията е ограничена" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Дънните в полетата „Парола на файла“ и „Потвърждаване на паролата на файла“ не съвпадат." + }, "hCaptchaUrl": { "message": "Адрес за hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Превключване на потребителя" }, + "alreadyHaveAccount": { + "message": "Вече имате регистрация?" + }, "options": { "message": "Настройки" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Изнасяне на трезора на организацията" + }, + "exportingOrganizationVaultDesc": { + "message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$. Записите в отделните лични трезори и тези в други организации няма да бъдат включени.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Заключено" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Направена е заявка за вписване" }, + "creatingAccountOn": { + "message": "Създаване на регистрация в" + }, + "checkYourEmail": { + "message": "Проверете е-пощата си" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Последвайте връзката в е-писмото изпратено до" + }, + "andContinueCreatingYourAccount": { + "message": "и продължете със създаването на регистрацията си." + }, + "noEmail": { + "message": "Не сте получили е-писмо?" + }, + "goBack": { + "message": "Върнете се назад" + }, + "toEditYourEmailAddress": { + "message": ", за да редактирате адреса на е-пощата си." + }, "exposedMasterPassword": { "message": "Разобличена главна парола" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Важно:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Бяхте отписан(а), тъй като идентификаторът Ви за достъп не може да бъде дешифриран. Впишете се отново, за да отстраните проблема." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Бяхте отписан(а), тъй като идентификаторът Ви за опресняване не може да бъде получен. Впишете се отново, за да отстраните проблема." + }, "masterPasswordHint": { "message": "Главната парола не може да бъде възстановена, ако я забравите!" }, @@ -2824,7 +2914,7 @@ "message": "Грешка при задаването на целева папка." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Преглед на елементите в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад към $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 30dfd127778..36a4dc85f20 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "আপনার অন-প্রাঙ্গনে হোস্টকৃত Bitwarden ইনস্টলেশনটির বেস URL উল্লেখ করুন।" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "প্রস্থানকৃত" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "আপনার লগইন মাত্রকালটির মেয়াদ শেষ হয়ে গেছে।" }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "সাহায্য" }, @@ -1299,12 +1317,42 @@ "message": "পাসওয়ার্ড হালনাগাদকৃত", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "ভল্ট রফতানি" }, "fileFormat": { "message": "ফাইলের ধরণ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 34fb1f6e8d0..491c9413f99 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Navedite osnovni URL vaše lokalne Bitwarden instalacije." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesija je istekla." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Pomoć" }, @@ -1299,12 +1317,42 @@ "message": "Lozinka ažurirana", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvezi trezor" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 68887770391..2fcc36136bb 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifiqueu l'URL base de la vostra instal·lació Bitwarden allotjada en un entorn propi." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Entorn personalitzat" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Desconnectat" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "La vostra sessió ha caducat." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Ajuda" }, @@ -1299,12 +1317,42 @@ "message": "Contrasenya actualitzada", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exporta caixa forta" }, "fileFormat": { "message": "Format de fitxer" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "Url hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Canvia de compte" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Opcions" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Bloquejat" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "S'ha sol·licitat inici de sessió" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Contrasenya mestra exposada" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "La contrasenya mestra no es pot recuperar si la oblideu!" }, diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 03f0a1e4b36..7dc48c3bf11 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Zadejte základní URL adresu vlastní hostované aplikace Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Zadejte základní URL adresu Vaší vlastní hostované aplikace Bitwarden. Příklad: https://bitwarden.spolecnost.cz" + }, + "selfHostedCustomEnvHeader": { + "message": "Pro rozšířená nastavení můžete zadat základní URL adresu každé služby zvlášť." + }, + "selfHostedEnvFormInvalid": { + "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." + }, "customEnvironment": { "message": "Vlastní prostředí" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odhlášení" }, + "loggedOutDesc": { + "message": "Byli jste odhlášeni ze svého účtu." + }, "loginExpired": { "message": "Platnost přihlášení vypršela." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Chyba aktualizace přístupového tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nebyly nalezeny žádné obnovovací tokeny nebo API klíče. Zkuste se odhlásit a znovu se přihlásit." + }, "help": { "message": "Nápověda" }, @@ -1299,12 +1317,42 @@ "message": "Heslo bylo aktualizováno", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exportovat z" + }, "exportVault": { "message": "Exportovat trezor" }, "fileFormat": { "message": "Formát souboru" }, + "fileEncryptedExportWarningDesc": { + "message": "Tento soubor exportu bude chráněn heslem a k dešifrování bude vyžadovat heslo souboru." + }, + "filePassword": { + "message": "Heslo souboru" + }, + "exportPasswordDescription": { + "message": "Toto heslo bude použito pro export a import tohoto souboru" + }, + "accountRestrictedOptionDescription": { + "message": "Pro zašifrování exportu a omezení importu pouze na aktuální účet Bitwardenu použijte šifrovací klíč Vašeho účtu odvozený z uživatelského jména a hlavního hesla." + }, + "passwordProtected": { + "message": "Chráněno heslem" + }, + "passwordProtectedOptionDescription": { + "message": "Nastavte heslo pro šifrování exportu a importujte ho do libovolného účtu Bitwardenu pomocí hesla pro dešifrování." + }, + "exportTypeHeading": { + "message": "Typ exportu" + }, + "accountRestricted": { + "message": "Účet je omezený" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Heslo souboru\" a \"Potvrzení hesla souboru\" se neshodují." + }, "hCaptchaUrl": { "message": "URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Přepnout účet" }, + "alreadyHaveAccount": { + "message": "Už máte účet?" + }, "options": { "message": "Volby" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exportování trezoru organizace" + }, + "exportingOrganizationVaultDesc": { + "message": "Exportován bude jen trezor organizace přidružený k položce $ORGANIZATION$. Osobní položky trezoru a položky z jiných organizací nebudou zahrnuty.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Uzamčeno" }, @@ -2376,7 +2439,7 @@ "message": "Tento požadavek již není platný." }, "approveLoginRequestDesc": { - "message": "Použijte toto zařízení pro schvalování žádostí o přihlášení z jiných zařízení." + "message": "Použije toto zařízení pro schvalování žádostí o přihlášení z jiných zařízení." }, "confirmLoginAtemptForMail": { "message": "Potvrďte pokus o přihlášení z $EMAIL$", @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Bylo vyžádáno přihlášení" }, + "creatingAccountOn": { + "message": "Vytváření účtu na" + }, + "checkYourEmail": { + "message": "Zkontrolujte Váš e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Následujte pokyny v e-mailu poslaném na" + }, + "andContinueCreatingYourAccount": { + "message": "a pokračujte ve vytváření Vašeho účtu." + }, + "noEmail": { + "message": "Žádný e-mail?" + }, + "goBack": { + "message": "Zpět" + }, + "toEditYourEmailAddress": { + "message": "pro úpravu Vaší e-mailové adresy." + }, "exposedMasterPassword": { "message": "Odhalené hlavní heslo" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Důležité:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Byli jste odhlášeni, protože Váš přístupový token nelze dešifrovat. Pro vyřešení tohoto problému se přihlaste znovu." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Byli jste odhlášeni, protože Váš obnovovací token nelze získat. Pro vyřešení tohoto problému se přihlaste znovu." + }, "masterPasswordHint": { "message": "Pokud zapomenete Vaše hlavní heslo, nebude možné jej obnovit!" }, @@ -2824,7 +2914,7 @@ "message": "Chyba při přiřazování cílové složky." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobrazit položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zpět do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Zpět", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odebrat $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 0124bb3a6f5..bec1804ae2a 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b5e65f44197..333bfcd1800 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Angiv grund-URL'en til den lokal-hostede Bitwarden-installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logget ud" }, + "loggedOutDesc": { + "message": "Der er blevet logget ud af kontoen." + }, "loginExpired": { "message": "Loginsessionen er udløbet." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Adgangstoken genopfriskningsfejl" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Ingen genopfriskningstoken eller API-nøgler fundet. Prøv at logge ud og dernæst ind igen." + }, "help": { "message": "Hjælp" }, @@ -1299,12 +1317,42 @@ "message": "Adgangskode opdateret", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Eksportér fra" + }, "exportVault": { "message": "Eksportér boks" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "Denne fileksport vil være adgangskodebeskyttet og kræve filadgangskoden at dekryptere." + }, + "filePassword": { + "message": "Filadgangskode" + }, + "exportPasswordDescription": { + "message": "Denne adgangskode vil blive brugt ved eksport og import af denne fil" + }, + "accountRestrictedOptionDescription": { + "message": "Brug kontokrypteringsnøglen, dannet af kontobrugernavn og Hovedadgangskode, for at kryptere eksporten og hindre import til andre end den aktuelle Bitwarden-konto." + }, + "passwordProtected": { + "message": "Adgangskodebeskyttet" + }, + "passwordProtectedOptionDescription": { + "message": "Opsæt en adgangskode til både at kryptere eksporten samt dekryptere denne ved import til enhver Bitwarden-konto." + }, + "exportTypeHeading": { + "message": "Eksporttype" + }, + "accountRestricted": { + "message": "Konto begrænset" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“Filadgangskode” og “Bekræft filadgangskode“ matcher ikke." + }, "hCaptchaUrl": { "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Skift konto" }, + "alreadyHaveAccount": { + "message": "Har allerede en konto?" + }, "options": { "message": "Indstillinger" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Eksport af organisationsboks" + }, + "exportingOrganizationVaultDesc": { + "message": "Kun organisationsboksen tilknyttet $ORGANIZATION$ eksporteres. Emner i individuelle bokse eller andre organisationer medtages ikke.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Låst" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Login anmodet" }, + "creatingAccountOn": { + "message": "Opretter konto på" + }, + "checkYourEmail": { + "message": "Tjek din e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Følg linket i e-mailen sendt til" + }, + "andContinueCreatingYourAccount": { + "message": "og fortsæt med kontooprettelsen." + }, + "noEmail": { + "message": "Ingen e-mail?" + }, + "goBack": { + "message": "Gå tilbage" + }, + "toEditYourEmailAddress": { + "message": "for at redigere e-mailadressen." + }, "exposedMasterPassword": { "message": "Kompromitteret hovedadgangskode" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Vigtigt:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Kontoen er blevet logget ud, fordi adgangstokenet ikke kunne dekrypteres. Log ind igen for at løse problemet." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Kontoen er blevet logget ud, fordi opfriskningstokenet ikke kunne dekrypteres. Log ind igen for at løse problemet." + }, "masterPasswordHint": { "message": "Hovedadgangskoden kan ikke gendannes, hvis den glemmes!" }, diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index fdc604986f3..769be2a4295 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Bitte gib die Basis-URL deiner selbst gehosteten Bitwarden-Installation an." }, + "selfHostedBaseUrlHint": { + "message": "Gib die Basis-URL deiner vor Ort gehosteten Bitwarden-Installation an. Beispiel: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Für eine erweiterte Konfiguration kannst du die Basis-URL jedes Dienstes unabhängig voneinander angeben." + }, + "selfHostedEnvFormInvalid": { + "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." + }, "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Abgemeldet" }, + "loggedOutDesc": { + "message": "Du wurdest von deinem Konto abgemeldet." + }, "loginExpired": { "message": "Deine Sitzung ist abgelaufen." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Zugangs-Token Aktualisierungsfehler" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Kein Aktualisierungs-Token oder API-Schlüssel gefunden. Bitte versuche dich ab- und wieder anzumelden." + }, "help": { "message": "Hilfe" }, @@ -1299,12 +1317,42 @@ "message": "Passwort aktualisiert", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export aus" + }, "exportVault": { "message": "Tresor exportieren" }, "fileFormat": { "message": "Dateiformat" }, + "fileEncryptedExportWarningDesc": { + "message": "Dieser Datei-Export ist passwortgeschützt und erfordert das Dateipasswort zum Entschlüsseln." + }, + "filePassword": { + "message": "Dateipasswort" + }, + "exportPasswordDescription": { + "message": "Dieses Passwort wird zum Exportieren und Importieren dieser Datei verwendet" + }, + "accountRestrictedOptionDescription": { + "message": "Verwende den Verschlüsselungsschlüssel deines Kontos, abgeleitet vom Benutzernamen und Master-Passwort, um den Export zu verschlüsseln und den Import auf das aktuelle Bitwarden-Konto zu beschränken." + }, + "passwordProtected": { + "message": "Passwortgeschützt" + }, + "passwordProtectedOptionDescription": { + "message": "Lege ein Dateipasswort fest, um den Export zu verschlüsseln und importiere ihn in ein beliebiges Bitwarden-Konto, wobei das Passwort zum Entschlüsseln genutzt wird." + }, + "exportTypeHeading": { + "message": "Exporttyp" + }, + "accountRestricted": { + "message": "Konto eingeschränkt" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "„Dateipasswort“ und „Dateipasswort bestätigen“ stimmen nicht überein." + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Konto wechseln" }, + "alreadyHaveAccount": { + "message": "Hast du bereits ein Konto?" + }, "options": { "message": "Optionen" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Tresor der Organisation wird exportiert" + }, + "exportingOrganizationVaultDesc": { + "message": "Nur der mit $ORGANIZATION$ verbundene Organisationstresor wird exportiert. Einträge in persönlichen Tresoren oder anderen Organisationen werden nicht berücksichtigt.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Gesperrt" }, @@ -2133,7 +2196,7 @@ "message": "Erzeuge ein E-Mail-Alias mit einem externen Weiterleitungsdienst." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "$SERVICENAME$ Fehler: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2151,7 +2214,7 @@ "description": "Displayed with the address on the forwarding service's configuration screen." }, "forwarderGeneratedByWithWebsite": { - "message": "Website: $WEBSITE$. Generated by Bitwarden.", + "message": "Webseite: $WEBSITE$. Von Bitwarden generiert.", "description": "Displayed with the address on the forwarding service's configuration screen.", "placeholders": { "WEBSITE": { @@ -2161,7 +2224,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ungültiger API-Token von $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2171,7 +2234,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ungültiger API-Token von $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2185,7 +2248,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Maskierte E-Mail-Konto-ID für $SERVICENAME$ konnte nicht abgerufen werden.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2195,7 +2258,7 @@ } }, "forwarderNoDomain": { - "message": "Invalid $SERVICENAME$ domain.", + "message": "Ungültige $SERVICENAME$-Domain.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2205,7 +2268,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Ungültige $SERVICENAME$-URL.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2215,7 +2278,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Unbekannter $SERVICENAME$-Fehler aufgetreten.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2225,7 +2288,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Unbekannter Weiterleitungsdienst: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Anmeldung angefordert" }, + "creatingAccountOn": { + "message": "Konto wird erstellt bei" + }, + "checkYourEmail": { + "message": "Überprüfe deine E-Mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Folge dem Link in der E-Mail an" + }, + "andContinueCreatingYourAccount": { + "message": "und fahre mit der Erstellung deines Kontos fort." + }, + "noEmail": { + "message": "Keine E-Mail?" + }, + "goBack": { + "message": "Geh zurück" + }, + "toEditYourEmailAddress": { + "message": ", um deine E-Mail-Adresse zu bearbeiten." + }, "exposedMasterPassword": { "message": "Kompromittiertes Master-Passwort" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Wichtig:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Du wurdest abgemeldet, weil dein Zugangs-Token nicht entschlüsselt werden konnte. Bitte melde dich erneut an, um dieses Problem zu beheben." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Du wurdest abgemeldet, weil dein Aktualisierungs-Token nicht entschlüsselt werden konnte. Bitte melde dich erneut an, um dieses Problem zu beheben." + }, "masterPasswordHint": { "message": "Dein Master-Passwort kann nicht wiederhergestellt werden, wenn du es vergisst!" }, @@ -2824,7 +2914,7 @@ "message": "Fehler beim Zuweisen des Ziel-Ordners." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Einträge in $NAME$ anzeigen", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zurück zu $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Zurück", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ entfernen", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 0f67e125902..fd261a8d032 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Καθορίστε τη βασική διεύθυνση URL, της εγκατάστασης του Bitwarden που φιλοξενείται στο χώρο σας." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Αποσύνδεση" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Η περίοδος σύνδεσης σας έχει λήξει." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Βοήθεια" }, @@ -1299,12 +1317,42 @@ "message": "Ο Κωδικός Ενημερώθηκε", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Εξαγωγή Vault" }, "fileFormat": { "message": "Μορφή Αρχείου" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Εναλλαγή λογαριασμού" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Ρυθμίσεις" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Κλειδωμένο" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Ζητήθηκε σύνδεση" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Εκτεθειμένος Κύριος Κωδικός Πρόσβασης" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Σημαντικό:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Ο κύριος κωδικός πρόσβασης δεν μπορεί να ανακτηθεί εάν τον ξεχάσετε!" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index dde17633788..82d57c205d4 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid" :{ + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken":{ + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc":{ + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2071,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2432,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index ec4ea5090b0..7f28fee4b2c 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organisation vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 6b96c526ee0..b3e9a40bb38 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organisation vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 223f5aa290c..4a429118976 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Adiaŭita" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 43600c4ff85..a3a2caf2e5a 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifica la URL base de tu instalación de Bitwarden de alojamiento propio." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Sesión terminada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Tu sesión ha expirado." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Ayuda" }, @@ -1299,12 +1317,42 @@ "message": "Contraseña actualizada", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exportar caja fuerte" }, "fileFormat": { "message": "Formato de archivo" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Cambiar cuenta" }, + "alreadyHaveAccount": { + "message": "¿Ya tienes una cuenta?" + }, "options": { "message": "Opciones" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Bloqueado" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Inicio de sesión solicitado" }, + "creatingAccountOn": { + "message": "Creando una cuenta en" + }, + "checkYourEmail": { + "message": "Comprueba tu correo electrónico" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Sigue el enlace en el correo electrónico enviado a" + }, + "andContinueCreatingYourAccount": { + "message": "y contunúa creando una cuenta." + }, + "noEmail": { + "message": "¿Sin correo electrónico?" + }, + "goBack": { + "message": "Volver" + }, + "toEditYourEmailAddress": { + "message": "para editar tu dirección de correo electrónico." + }, "exposedMasterPassword": { "message": "Contraseña maestra comprometida" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Importante:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Tu contraseña maestra no se puede recuperar si la olvidas" }, @@ -2824,7 +2914,7 @@ "message": "Error al asignar la carpeta de destino." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Ver elementos en $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Volver a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Atrás", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Eliminar $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 8203e38d29e..a38790c47ab 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Kohandatud keskkond" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Välja logitud" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sessioon on aegunud." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Abi" }, @@ -1299,12 +1317,42 @@ "message": "Parool on uuendatud", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Ekspordi hoidla" }, "fileFormat": { "message": "Failivorming" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Vaheta kontot" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Valikud" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Lukustatud" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Sisselogimise päring on saadetud" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Ülemparool on haavatav" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Tähtis:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Ülemparooli ei saa taastada, kui sa selle unustama peaksid!" }, diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index dbfee64137c..a3b0f0f87b2 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Bitwarden instalatzeko, zehaztu ostatatze propioaren oinarrizko URL-a." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Ingurune pertsonalizatua" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Saioa itxita" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Saioa amaitu da." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Laguntza" }, @@ -1299,12 +1317,42 @@ "message": "Pasahitza eguneratu da", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Esportatu kutxa gotorra" }, "fileFormat": { "message": "Fitxategiaren formatua" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Aldatu kontua" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Aukerak" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Blokeatuta" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 979b0a45ffa..7fe2758ec40 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "نشانی اینترنتی پایه فرضی نصب Bitwarden میزبانی شده را مشخص کنید." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "محیط سفارشی" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "خارج شد" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "راهنما" }, @@ -1299,12 +1317,42 @@ "message": "کلمه عبور به‌روزرسانی شد", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "برون ریزی گاوصندوق" }, "fileFormat": { "message": "فرمت پرونده" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "نشانی اینترنتی hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "تعویض حساب کاربری" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "گزینه‌ها" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "قفل شده" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "ورود الزامیست" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "کلمه عبور اصلی افشا شده" }, @@ -2408,6 +2492,12 @@ "important": { "message": "مهم:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "کلمه عبور اصلی شما در صورت فراموشی قابل بازیابی نیست!" }, diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 3d044cc238c..4586511cab4 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Määritä omassa palvelinympäristössäsi suoritettavan Bitwarden-asennuksen pääverkkotunnus." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Kirjauduttu ulos" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Kirjautumisistuntosi on erääntynyt." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Ohje" }, @@ -1299,12 +1317,42 @@ "message": "Salasana vaihdettiin", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Vie lähteestä" + }, "exportVault": { "message": "Vie holvi" }, "fileFormat": { "message": "Tiedostomuoto" }, + "fileEncryptedExportWarningDesc": { + "message": "Tämä vientitiedosto suojataan salasanalla, joka on syötettävä ja salauksen purkamiseksi." + }, + "filePassword": { + "message": "Tiedoston salasana" + }, + "exportPasswordDescription": { + "message": "Tätä salasanaa käytetään tämän tiedoston viennissä ja tuonnissa" + }, + "accountRestrictedOptionDescription": { + "message": "Salaa vienti ja rajoita tuonti vain nykyiselle Bitwarden-tilille tilisi käyttäjätunnukseen ja pääsalasanaan pohjautuvalla salausavaimella." + }, + "passwordProtected": { + "message": "Salasanasuojattu" + }, + "passwordProtectedOptionDescription": { + "message": "Salaa vientitiedosto salasanalla, joka mahdollistaa sen tuonnin mille tahansa Bitwarden-tilille." + }, + "exportTypeHeading": { + "message": "Viennin tyyppi" + }, + "accountRestricted": { + "message": "Tiliä on rajoitettu" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Tiedoston salasana\" ja \"Vahvista tiedoston salasana\" eivät täsmää." + }, "hCaptchaUrl": { "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Vaihda tiliä" }, + "alreadyHaveAccount": { + "message": "Onko sinulla jo tili?" + }, "options": { "message": "Valinnat" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Organisaation holvin vienti" + }, + "exportingOrganizationVaultDesc": { + "message": "Vain organisaatioon $ORGANIZATION$ liitetyn holvin kohteet viedään. Yksityisen holvin ja muiden organisaatioiden kohteita ei sisällytetä.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Lukittu" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Kirjautumista pyydetty" }, + "creatingAccountOn": { + "message": "Luodaan tili palvelimelle" + }, + "checkYourEmail": { + "message": "Tarkasta sähköpostisi" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Seuraa viestin linkkiä, joka lähetettiin sähköpostitse osoitteeseen" + }, + "andContinueCreatingYourAccount": { + "message": "ja jatka tilin luontia." + }, + "noEmail": { + "message": "Etkö saanut viestiä?" + }, + "goBack": { + "message": "Palaa takaisin" + }, + "toEditYourEmailAddress": { + "message": "muokataksesi sähköpostiosoitettasi." + }, "exposedMasterPassword": { "message": "Paljastunut pääsalasana" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Tärkeää:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Pääsalasanasi palauttaminen ei ole mahdollista, jos unohdat sen!" }, @@ -2824,7 +2914,7 @@ "message": "Virhe määritettäessä kohdekansiota." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Näytä kohteen $NAME$ kohteet", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Palaa kohteeseen $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Takaisin", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Poista $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index b95ecc56a6e..d4b76de533d 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Tukuyin ang base URL ng iyong on premises na naka host sa pag install ng Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Kapaligirang Custom" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Umalis na" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Nag-expire na ang iyong session sa login." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Tulong" }, @@ -1299,12 +1317,42 @@ "message": "Na-update ang password", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "I-export vault" }, "fileFormat": { "message": "Format ng file" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Lumipat ng account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Mga Pagpipilian" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Naka-lock" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Hiniling ang pag log in" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Nakalantad na Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Mahalaga:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Hindi mababawi ang password ng master mo kung nakalimutan mo ito!" }, diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 9e547762e65..a4288526277 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Spécifiez l'URL de base de votre installation Bitwarden auto-hébergée." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Environnement personnalisé" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Déconnecté" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Votre session a expiré." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Aide" }, @@ -1299,12 +1317,42 @@ "message": "Mot de passe mis à jour", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exporter le coffre" }, "fileFormat": { "message": "Format de fichier" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Changer de compte" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Verrouillé" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Connexion demandée" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Mot de passe principal exposé" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important :" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Votre mot de passe principal ne peut pas être récupéré si vous l'oubliez !" }, diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index d8cb372359c..4d0d96038f6 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 6ab0859fcc6..b559652ce3d 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "הזן את כתובת השרת המקומי של Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "בוצעה יציאה" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "עזרה" }, @@ -1299,12 +1317,42 @@ "message": "הסיסמה עודכנה", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "יצוא כספת" }, "fileFormat": { "message": "תבנית קובץ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "כתובת אתר hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "החלף חשבון" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "אפשרויות" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "נָעוּל" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "חשוב:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index bb4d063747e..bc4bf462f6b 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index 049882594c6..2adc28e3ced 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Navedi osnovni URL svoje lokalno smještene Bitwarden instalacije." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesija je istekla." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Pomoć" }, @@ -1299,12 +1317,42 @@ "message": "Lozinka ažurirana", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvezi trezor" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Promijeni račun" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Mogućnosti" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Zaključano" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Zatražena je prijava" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Ukradena glavna lozinka" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Važno:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Glavnu lozinku nije moguće oporaviti ako ju zaboraviš!" }, diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index c2f515bdbf8..568dc954e92 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "A helyileg üzemeltetett Bitwarden telepítés webcímének megadása." }, + "selfHostedBaseUrlHint": { + "message": "Adjuk meg a helyileg tárolt Bitwarden telepítés alap webcímét. Példa: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Speciális konfigurációhoz külön-külön megadhatjuk az egyes szolgáltatások alap webcímét." + }, + "selfHostedEnvFormInvalid": { + "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." + }, "customEnvironment": { "message": "Egyedi környezet" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Megtörtént a kijelentkezés." }, + "loggedOutDesc": { + "message": "Megtörtént a kijelentkezés a fiókból." + }, "loginExpired": { "message": "A bejelentkezési munkamenet lejárt." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Hozzáférési vezérjel frissítési hiba" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nem található frissítési vezérjel vagy API kulcs. Próbáljunk meg kijelentkezni, majd újra bejelentkezni." + }, "help": { "message": "Súgó" }, @@ -1299,12 +1317,42 @@ "message": "A jelszó frissítésre került.", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exportálás innen:" + }, "exportVault": { "message": "Széf exportálása" }, "fileFormat": { "message": "Fájlformátum" }, + "fileEncryptedExportWarningDesc": { + "message": "Ez a fájl exportálás jelszóval védett és a visszafejtéshez a fájl jelszó megadása szükséges." + }, + "filePassword": { + "message": "Fájl jelszó" + }, + "exportPasswordDescription": { + "message": "Ezt a jelszó kerül használatba a fájl exportálására és importálására." + }, + "accountRestrictedOptionDescription": { + "message": "Használjuk a fiók felhasználónevéből és mesterjelszavából származó fióktitkosítási kulcsot az exportálás titkosításához és az importálást csak az aktuális Bitwarden fiókra korlátozzuk." + }, + "passwordProtected": { + "message": "Jelszóval védett" + }, + "passwordProtectedOptionDescription": { + "message": "Állítsunk be egy fájl jelszót az exportálás titkosításához és importáljuk azt bármely Bitwarden fiókba a visszafejtéshez használt jelszó használatával." + }, + "exportTypeHeading": { + "message": "Exportálási típus" + }, + "accountRestricted": { + "message": "Korlátozott fiók" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "A “Fájl jelszó” és a “Fájl jelszó megerősítés“ nem egyezik." + }, "hCaptchaUrl": { "message": "hCaptcha webcím", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Fiók váltása" }, + "alreadyHaveAccount": { + "message": "Van már saját fiók?" + }, "options": { "message": "Opciók" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Szervezeti széf exportálása" + }, + "exportingOrganizationVaultDesc": { + "message": "Csak $ORGANIZATION$ névvel társított szervezeti széf kerül exportálásra. Ebbe nem kerülnek be a személyes és más szervezeti széf elemek.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Lezárva" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Bejelentkezés megkérve" }, + "creatingAccountOn": { + "message": "Fiók létrehozása:" + }, + "checkYourEmail": { + "message": "Email cím postaláda ellenőrzése" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kövessük a hivatkozást az elküldött emailben" + }, + "andContinueCreatingYourAccount": { + "message": "és folytassuk a fiók létrehozását." + }, + "noEmail": { + "message": "Nem érkezett email?" + }, + "goBack": { + "message": "Vissza" + }, + "toEditYourEmailAddress": { + "message": "az email cím szerkesztéséhez." + }, "exposedMasterPassword": { "message": "Kiszivárgott mesterjelszó" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Fontos:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Megtörtént a kijelentkezés, mert a hozzáférési vezérjelet nem sikerült visszafejteni. Jelentkezzünk be újra a probléma megoldásához." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Megtörtént a kijelentkezés, mert a hozzáférési vezérjelet nem sikerült visszafejteni. Jelentkezzünk be újra a probléma megoldásához." + }, "masterPasswordHint": { "message": "Az elfelejtett mesterjelszó nem állítható helyre, ha elfelejtik!" }, @@ -2824,7 +2914,7 @@ "message": "Hiba történt a célmappa hozzárendelése során." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ elemek megtekintése", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Vissza: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Vissza", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ eltávolítása", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index ba0c9095ad1..545901170fd 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Lingkungan Kustom" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Keluar" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Bantuan" }, @@ -1299,12 +1317,42 @@ "message": "Kata Sandi telah Diperbarui", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Ekspor Brankas" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Pindah akun" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Pilihan" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Terkunci" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Kata Sandi Utama terekspos" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Penting:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Kata sandi utama Anda tidak dapat dipulihkan jika Anda lupa!" }, diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index b7335e3ea55..72f2115460f 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specifica l'URL principale della tua installazione self-hosted di Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Specifica lo URL principale della tua installazione self-hosted di Bitwarden. Esempio: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Per la configurazione avanzata, puoi specificare lo URL di base di ciascun servizio in modo indipendente." + }, + "selfHostedEnvFormInvalid": { + "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." + }, "customEnvironment": { "message": "Ambiente personalizzato" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Uscito" }, + "loggedOutDesc": { + "message": "Sei stato fatto uscire dal tuo account." + }, "loginExpired": { "message": "La tua sessione è scaduta." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Errore di aggiornamento del token di accesso" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nessun token di aggiornamento o chiave API trovati. Prova ad uscire ed entrare di nuovo." + }, "help": { "message": "Aiuto" }, @@ -1299,12 +1317,42 @@ "message": "Password aggiornata", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Esporta da" + }, "exportVault": { "message": "Esporta cassaforte" }, "fileFormat": { "message": "Formato file" }, + "fileEncryptedExportWarningDesc": { + "message": "Questo file esportato sarà protetto e richiederà la password del file per decifrarlo." + }, + "filePassword": { + "message": "Password del file" + }, + "exportPasswordDescription": { + "message": "La password sarà utilizzata per importare ed esportare questo file" + }, + "accountRestrictedOptionDescription": { + "message": "Usa la chiave di crittografia dell'account, derivata dal nome utente e dalla password principale del tuo account, per crittografare il file di esportazione e limitare l'importazione solo all'account Bitwarden corrente." + }, + "passwordProtected": { + "message": "Protetto da password" + }, + "passwordProtectedOptionDescription": { + "message": "Imposta una password del file per crittografare il file esportato e importarlo in qualsiasi account Bitwarden usando la password per decrittografarlo." + }, + "exportTypeHeading": { + "message": "Tipo di esportazione" + }, + "accountRestricted": { + "message": "Account limitato" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Le due password del file non corrispondono." + }, "hCaptchaUrl": { "message": "URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Cambia account" }, + "alreadyHaveAccount": { + "message": "Hai già un account?" + }, "options": { "message": "Opzioni" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Esportando cassaforte dell'organizzazione" + }, + "exportingOrganizationVaultDesc": { + "message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata. Elementi nelle casseforti individuali o in altre organizzazioni non saranno inclusi.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Bloccato" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Accesso richiesto" }, + "creatingAccountOn": { + "message": "Creazione account su" + }, + "checkYourEmail": { + "message": "Controlli la tua email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Segui il link nell'email inviata a" + }, + "andContinueCreatingYourAccount": { + "message": "e continua a creare il tuo account." + }, + "noEmail": { + "message": "Nessuna email?" + }, + "goBack": { + "message": "Torna indietro" + }, + "toEditYourEmailAddress": { + "message": "per modificare il tuo indirizzo email." + }, "exposedMasterPassword": { "message": "Password principale violata" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Importante:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Sei stato fatto uscire perché non è stato possibile decrittografare il tuo token di accesso. Entra di nuovo per risolvere il problema." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Sei stato fatto uscire perché non è stato possibile recuperare il token di aggiornamento. Entra di nuovo per risolvere il problema." + }, "masterPasswordHint": { "message": "La tua password principale non può essere recuperata se la dimentichi!" }, @@ -2824,7 +2914,7 @@ "message": "Errore nell'assegnazione della cartella di destinazione." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizza gli elementi in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Torna a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Indietro", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Rimuovi $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 5c134f15ba7..94e96a98b37 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "セルフホスティングしている Bitwarden のベース URL を指定してください。" }, + "selfHostedBaseUrlHint": { + "message": "オンプレミスホストした Bitwarden のベース URL を指定してください。例: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "高度な設定では、各サービスのベース URL を個別に指定できます。" + }, + "selfHostedEnvFormInvalid": { + "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" + }, "customEnvironment": { "message": "カスタム環境" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "ログアウトしました" }, + "loggedOutDesc": { + "message": "アカウントからログアウトしました。" + }, "loginExpired": { "message": "ログインセッションの有効期限が切れています。" }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "アクセストークンの更新エラー" + }, + "errorRefreshingAccessTokenDesc": { + "message": "リフレッシュトークンや API キーが見つかりませんでした。ログアウトして再度ログインしてください。" + }, "help": { "message": "ヘルプ" }, @@ -1299,12 +1317,42 @@ "message": "パスワード更新日", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "エクスポート元" + }, "exportVault": { "message": "保管庫のエクスポート" }, "fileFormat": { "message": "ファイル形式" }, + "fileEncryptedExportWarningDesc": { + "message": "エクスポートするファイルはパスワードで保護され、復号するにはファイルパスワードが必要になります。" + }, + "filePassword": { + "message": "ファイルパスワード" + }, + "exportPasswordDescription": { + "message": "このパスワードはこのファイルのエクスポートとインポート時に使用します" + }, + "accountRestrictedOptionDescription": { + "message": "アカウントのユーザー名とマスターパスワードから得られる暗号化キーを使用してエクスポートするデータを暗号化し、現在の Bitwarden アカウントのみがインポートできるよう制限します。" + }, + "passwordProtected": { + "message": "パスワード保護あり" + }, + "passwordProtectedOptionDescription": { + "message": "エクスポートを暗号化するためのファイルパスワードを設定します。そのパスワードを使用して、任意の Bitwarden アカウントにインポートします。" + }, + "exportTypeHeading": { + "message": "エクスポートの種類" + }, + "accountRestricted": { + "message": "アカウント制限" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "「ファイルパスワード」と「ファイルパスワードの確認」が一致しません。" + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "アカウント切替" }, + "alreadyHaveAccount": { + "message": "すでにアカウントをお持ちですか?" + }, "options": { "message": "オプション" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "組織保管庫のエクスポート" + }, + "exportingOrganizationVaultDesc": { + "message": "$ORGANIZATION$ に関連付けられた組織保管庫のみがエクスポートされます。個々の保管庫または他の組織にあるアイテムは含まれません。", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "ロック中" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "ログインリクエスト済み" }, + "creatingAccountOn": { + "message": "アカウント作成:" + }, + "checkYourEmail": { + "message": "メールをご確認ください" + }, + "followTheLinkInTheEmailSentTo": { + "message": "アカウントの作成を続けるには" + }, + "andContinueCreatingYourAccount": { + "message": "に送られたメールのリンクを開いてください。" + }, + "noEmail": { + "message": "メールがありませんか?" + }, + "goBack": { + "message": "戻って" + }, + "toEditYourEmailAddress": { + "message": "メールアドレスを編集してください。" + }, "exposedMasterPassword": { "message": "流出したマスターパスワード" }, @@ -2408,6 +2492,12 @@ "important": { "message": "重要" }, + "accessTokenUnableToBeDecrypted": { + "message": "アクセストークンを復号できなかったためログアウトしました。この問題を解決するためには再度ログインしてください。" + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "リフレッシュトークンを取得できなかったためログアウトしました。この問題を解決するためには再度ログインしてください。" + }, "masterPasswordHint": { "message": "マスターパスワードを忘れた場合は復元できません!" }, @@ -2824,7 +2914,7 @@ "message": "ターゲットフォルダーの割り当てに失敗しました。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ のアイテムを表示", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "$NAME$ に戻る", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index d8cb372359c..4d0d96038f6 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index d8cb372359c..4d0d96038f6 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index 4b654e92327..88e371b67fc 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "ನಿಮ್ಮ ಆನ್-ಪ್ರಮೇಯ ಹೋಸ್ಟ್ ಮಾಡಿದ ಬಿಟ್‌ವಾರ್ಡೆನ್ ಸ್ಥಾಪನೆಯ ಮೂಲ URL ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸಿ." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "ಲಾಗ್ ಔಟ್" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "ನಿಮ್ಮ ಲಾಗಿನ್ ಸೆಷನ್ ಅವಧಿ ಮೀರಿದೆ." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "ಸಹಾಯ" }, @@ -1299,12 +1317,42 @@ "message": "ಪಾಸ್ವರ್ಡ್ ನವೀಕರಿಸಲಾಗಿದೆ", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "ರಫ್ತು ವಾಲ್ಟ್" }, "fileFormat": { "message": "ಕಡತದ ಮಾದರಿ" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 8db205e66fa..672e775af4e 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "온-프레미스 Bitwarden이 호스팅되고 있는 서버의 기본 URL을 지정하세요." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "사용자 지정 환경" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "로그아웃됨" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "로그인 세션이 만료되었습니다." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "도움말" }, @@ -1299,12 +1317,42 @@ "message": "비밀번호 업데이트됨", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "보관함 내보내기" }, "fileFormat": { "message": "파일 형식" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "계정 전환" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "선택" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "잠김" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "로그인 요청됨" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "중요:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "마스터 비밀번호를 잊어버리면 복구할 수 없습니다!" }, diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 22f2ad8283c..68698617d47 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Nurodykite pagrindinį URL adresą savo patalpose esančio Bitwarden įdiegimo." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Individualizuota aplinka" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Atsijungta" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesijos laikas baigėsi." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Pagalba" }, @@ -1299,12 +1317,42 @@ "message": "Slaptažodis atnaujintas", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Eksportuoti saugyklą" }, "fileFormat": { "message": "Failo formatas" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha nuoroda", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Perjungti paskyrą" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Nustatymai" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Užrakinta" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Prašoma prisijungti" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Atskleistas pagrindinis slaptažodis" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Svarbu:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Pagrindinis slaptažodis negali būti atkurtas, jei jį pamiršite!" }, diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index a1f810ee4af..4579c68742e 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Norādīt pašuzstādīta Bitwarden pamata URL." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Pielāgota vide" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Atteicies" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Pieteikšanās sesija ir beigusies." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Palīdzība" }, @@ -1299,12 +1317,42 @@ "message": "Parole atjaunināta", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Izgūt no" + }, "exportVault": { "message": "Izgūt glabātavas saturu" }, "fileFormat": { "message": "Datnes veids" }, + "fileEncryptedExportWarningDesc": { + "message": "Šī datņu izgūšana būs aizsargāta ar paroli, un būs nepieciešama datnes parole, lai to atšifrētu." + }, + "filePassword": { + "message": "Datnes parole" + }, + "exportPasswordDescription": { + "message": "Šī parole tiks izmantota, lai izgūtu un ievietotu šo datni" + }, + "accountRestrictedOptionDescription": { + "message": "Jāizmanto konta šifrēšanas atslēga, kas iegūta no lietotājvārda un galvenās paroles, lai šifrētu izguvi un atļautu ievietošanu tikai pašreizējā Bitwarden kontā." + }, + "passwordProtected": { + "message": "Aizsargāts ar paroli" + }, + "passwordProtectedOptionDescription": { + "message": "Uzstādīt paroli, lai šifrētu izguvi un tad to ievietotu jebkurā Bitwarden kontā, izmantojot atšifrēšanas paroli." + }, + "exportTypeHeading": { + "message": "Izgūšanas veids" + }, + "accountRestricted": { + "message": "Konts ir ierobežots" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Datnes parole\" un \"Apstiprināt datnes paroli\" vērtības nesakrīt." + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -1347,7 +1395,7 @@ "message": "Apstiprināt glabātavas satura izgūšanu" }, "exportWarningDesc": { - "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Izdzēst to uzreiz pēc izmantošanas." + "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas." }, "encExportKeyWarningDesc": { "message": "Šī izguve šifrē datus ar konta šifrēšanas atslēgu. Ja tā jebkad tiks mainīta, izvadi vajadzētu veikt vēlreiz, jo vairs nebūs iespējams atšifrēt šo datni." @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Pārslēgties starp kontiem" }, + "alreadyHaveAccount": { + "message": "Jau ir konts?" + }, "options": { "message": "Iespējas" }, @@ -2057,7 +2108,7 @@ "message": "Sesijai iestājās noildze. Lūgums mēģināt pieteikties vēlreiz." }, "exportingPersonalVaultTitle": { - "message": "Izdod personīgo glabātavu" + "message": "Izgūst personīgo glabātavu" }, "exportingIndividualVaultDescription": { "message": "Tiks izgūti tikai atsevišķi glabātavas vienumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti. Tiks izgūta tikai glabātavas vienumu informācija, un saistītie pielikumi netiks iekļauti.", @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Izgūst apvienības glabātavu" + }, + "exportingOrganizationVaultDesc": { + "message": "Tiks izgūta tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Aizslēgta" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Pieprasīta pieteikšanās" }, + "creatingAccountOn": { + "message": "Tiek veidots konts" + }, + "checkYourEmail": { + "message": "Jāpārbauda e-pasts" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Jāatver saite, kas tika nosūtīta uz e-pasta adresi" + }, + "andContinueCreatingYourAccount": { + "message": "un jāturpina sava konta izveide." + }, + "noEmail": { + "message": "Nav e-pasta?" + }, + "goBack": { + "message": "Atgriezties" + }, + "toEditYourEmailAddress": { + "message": ", lai labotu savu e-pasta adresi." + }, "exposedMasterPassword": { "message": "Noplūdusi galvenā parole" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Svarīgi:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Galvenā parole nevar tikt atgūta, ja tā ir aizmirsta!" }, @@ -2661,7 +2751,7 @@ "message": "Kļūda izguves datnes atšifrēšanā. Izmantotā atslēga neatbilst tai, kas tika izmantota satura izgūšanai." }, "invalidFilePassword": { - "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izdošanas datnes izveidošanas brīdī." + "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izgūšanas datnes izveidošanas brīdī." }, "importDestination": { "message": "Ievietošanas galamērķis" @@ -2824,7 +2914,7 @@ "message": "Kļūda mērķa mapes piešķiršanā." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Skatīt $NAME$ vienumus", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Atgriezties $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Atpakaļ", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Noņemt $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index a4d6ff7026c..dc8a3ebdeeb 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Navedite osnovni URL vaše lokalne instalacije (HOST) Bitwardena." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Vaša sesija je istekla." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Pomoć" }, @@ -1299,12 +1317,42 @@ "message": "Lozinka ažurirana", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvezi trezor" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 38ba942874e..1316189317b 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "നിങ്ങളുടെ പരിസരത്ത് ചെയ്യുന്ന Bitwarden ഇൻസ്റ്റാളേഷന്റെ അടിസ്ഥാന URL വ്യക്തമാക്കുക." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "ലോഗ് ഔട്ട് ചെയ്തിരിക്കുന്നു" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "നിങ്ങളുടെ പ്രവർത്തന സമയം കഴിഞ്ഞിരിക്കുന്നു." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "സഹായം" }, @@ -1299,12 +1317,42 @@ "message": "പാസ്‍വേഡ് പുതുക്കി", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "വാൾട് എക്സ്പോർട്" }, "fileFormat": { "message": "ഫയൽ ഫോർമാറ്റ്" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index d8cb372359c..4d0d96038f6 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 78b62a34a0b..1ec3bb1630d 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 6826acb6a8b..149d0f75773 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Spesifiser grunn-nettadressen til din selvbetjente Bitwarden-installasjon." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logget av" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Hjelp" }, @@ -1299,12 +1317,42 @@ "message": "Passordet ble oppdatert den", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Eksporter hvelvet" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Bytt konto" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Alternativer" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Låst" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Eksponert hovedpassord" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Viktig:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 0f138a2c418..3ad79ad8bf6 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -27,28 +27,28 @@ "message": "सुरक्षित नोट" }, "folders": { - "message": "Folders" + "message": "फोल्डरहरू" }, "collections": { - "message": "Collections" + "message": "संग्रह" }, "searchVault": { - "message": "Search vault" + "message": "भल्टमा खोज्नुहोस्" }, "addItem": { - "message": "Add item" + "message": "आइटम थप्नुहोस्" }, "shared": { - "message": "Shared" + "message": "साझा गरियो" }, "share": { - "message": "Share" + "message": "शेयर" }, "moveToOrganization": { - "message": "Move to organization" + "message": "संस्थामा सार्नुहोस्" }, "movedItemToOrg": { - "message": "$ITEMNAME$ moved to $ORGNAME$", + "message": "$ITEMNAME$ लाई $ORGNAME$ मा सारियो", "placeholders": { "itemname": { "content": "$1", @@ -61,16 +61,16 @@ } }, "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": "तपाईंले यो आइटम सार्न चाहनुहुने संस्था छान्नुहोस्। संस्थामा सर्दा आइटमको स्वामित्व त्यो संस्थामा हस्तान्तरण हुन्छ। एकपटक सारिएपछि तपाईं अब यस आइटमको प्रत्यक्ष मालिक हुनुहुनेछैन।" }, "attachments": { - "message": "Attachments" + "message": "अट्याचमेंटहरू" }, "viewItem": { - "message": "View item" + "message": "आइटम हेर्नुहोस्" }, "name": { - "message": "Name" + "message": "नाम" }, "uri": { "message": "URI" @@ -86,120 +86,120 @@ } }, "newUri": { - "message": "New URI" + "message": "नयाँ URI" }, "username": { - "message": "Username" + "message": "युजरनेम" }, "password": { - "message": "Password" + "message": "पासवर्ड" }, "passphrase": { - "message": "Passphrase" + "message": "पासफ्रेज" }, "editItem": { - "message": "Edit item" + "message": "आइटम सम्पादन" }, "emailAddress": { - "message": "Email address" + "message": "इमेल ठेगाना" }, "verificationCodeTotp": { - "message": "Verification code (TOTP)" + "message": "प्रमाणीकरण कोड (TOTP)" }, "website": { - "message": "Website" + "message": "वेबसाइट" }, "notes": { - "message": "Notes" + "message": "टिपोट" }, "customFields": { - "message": "Custom fields" + "message": "अनुकूलन फिल्डहरू" }, "launch": { - "message": "Launch" + "message": "वेबसाइटमा जानुहोस्" }, "copyValue": { "message": "Copy value", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { - "message": "Minimize when copying to clipboard" + "message": "क्लिपबोर्डमा प्रतिलिपि गर्दा मिनीमाइज गर्नुहोस्" }, "minimizeOnCopyToClipboardDesc": { - "message": "Minimize application when copying an item's data to the clipboard." + "message": "क्लिपबोर्डमा आइटमको डेटा प्रतिलिपि गर्दा अनुप्रयोगलाई मिनिमाइज गर्नुहोस्।" }, "toggleVisibility": { - "message": "Toggle visibility" + "message": "दृश्यता टगल गर्नुहोस्" }, "toggleCollapse": { "message": "Toggle collapse", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Cardholder name" + "message": "कार्डधारकको नाम" }, "number": { - "message": "Number" + "message": "नम्बर" }, "brand": { - "message": "Brand" + "message": "ब्राण्ड" }, "expiration": { - "message": "Expiration" + "message": "म्याद समाप्ति" }, "securityCode": { - "message": "Security code" + "message": "सुरक्षा कोड (CVV)" }, "identityName": { - "message": "Identity name" + "message": "पहिचान नाम" }, "company": { - "message": "Company" + "message": "कम्पनी" }, "ssn": { - "message": "Social Security number" + "message": "सामाजिक सुरक्षा नम्बर" }, "passportNumber": { - "message": "Passport number" + "message": "राहदानी नम्बर" }, "licenseNumber": { - "message": "License number" + "message": "लाइसेन्स नम्बर" }, "email": { - "message": "Email" + "message": "ईमेल" }, "phone": { - "message": "Phone" + "message": "फोन नं" }, "address": { - "message": "Address" + "message": "ठेगाना" }, "premiumRequired": { - "message": "Premium required" + "message": "प्रिमियम आवश्यक छ" }, "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." + "message": "यो सुविधा प्रयोग गर्न प्रिमियम सदस्यता आवश्यक छ।" }, "errorOccurred": { - "message": "An error has occurred." + "message": "त्रुटि देखा पर्‍यो।" }, "error": { - "message": "Error" + "message": "त्रुटि" }, "january": { - "message": "January" + "message": "जनवरी" }, "february": { - "message": "February" + "message": "फेब्रुअरी" }, "march": { - "message": "March" + "message": "मार्च" }, "april": { - "message": "April" + "message": "अप्रिल" }, "may": { - "message": "May" + "message": "मे" }, "june": { "message": "June" @@ -349,62 +349,62 @@ "message": "Name is required." }, "addedItem": { - "message": "Item added" + "message": "आइटम थपियो" }, "editedItem": { - "message": "Item saved" + "message": "आइटम सेभ गरियो" }, "deleteItem": { - "message": "Delete item" + "message": "आइटम हटाउनुहोस्" }, "deleteFolder": { - "message": "Delete folder" + "message": "फोल्डर हटाउनुहोस्" }, "deleteAttachment": { - "message": "Delete attachment" + "message": "अट्याचमेंट हटाउनुहोस्" }, "deleteItemConfirmation": { - "message": "Do you really want to send to the trash?" + "message": "के तपाई साँच्चै रद्दीटोकरीमा पठाउन चाहनुहुन्छ?" }, "deletedItem": { - "message": "Item sent to trash" + "message": "आइटम रद्दीटोकरीमा पठाइयो" }, "overwritePasswordConfirmation": { - "message": "Are you sure you want to overwrite the current password?" + "message": "के तपाइँ निश्चित हुनुहुन्छ कि तपाइँ हालको पासवर्ड अधिलेखन (ओभरराइट) गर्न चाहनुहुन्छ?" }, "overwriteUsername": { - "message": "Overwrite username" + "message": "युजरनेम अधिलेखन गर्नुहोस्" }, "overwriteUsernameConfirmation": { - "message": "Are you sure you want to overwrite the current username?" + "message": "के तपाइँ निश्चित हुनुहुन्छ कि तपाइँ हालको युजरनेम अधिलेखन (ओभरराइट) गर्न चाहनुहुन्छ?" }, "noneFolder": { - "message": "No folder", + "message": "कुनै फोल्डर हैन", "description": "This is the folder for uncategorized items" }, "addFolder": { - "message": "Add folder" + "message": "फोल्डर थप्नुहोस्" }, "editFolder": { - "message": "Edit folder" + "message": "फोल्डर सम्पादन गर्नुहोस्" }, "regeneratePassword": { - "message": "Regenerate password" + "message": "पासवर्ड पुन: उत्पन्न गर्नुहोस्" }, "copyPassword": { - "message": "Copy password" + "message": "पासवर्ड प्रतिलिपि गर्नुहोस्" }, "copyUri": { - "message": "Copy URI" + "message": "URI प्रतिलिपि गर्नुहोस्" }, "copyVerificationCodeTotp": { - "message": "Copy verification code (TOTP)" + "message": "प्रमाणीकरण कोड (TOTP) प्रतिलिपि गर्नुहोस्" }, "length": { - "message": "Length" + "message": "लम्बाइ" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "न्यूनतम पासवर्ड लम्बाइ" }, "uppercase": { "message": "Uppercase (A-Z)" @@ -413,45 +413,45 @@ "message": "Lowercase (a-z)" }, "numbers": { - "message": "Numbers (0-9)" + "message": "नम्बरहरू (0-9)" }, "specialCharacters": { - "message": "Special characters (!@#$%^&*)" + "message": "विशेष अक्षरहरू (!@#$%^&*)" }, "numWords": { - "message": "Number of words" + "message": "शब्द संख्या" }, "wordSeparator": { - "message": "Word separator" + "message": "शब्द विभाजक" }, "capitalize": { "message": "Capitalize", "description": "Make the first letter of a word uppercase." }, "includeNumber": { - "message": "Include number" + "message": "नम्बर समावेश गर्नुहोस्" }, "close": { - "message": "Close" + "message": "बन्द गर्नुहोस्" }, "minNumbers": { - "message": "Minimum numbers" + "message": "न्यूनतम नम्बरहरू" }, "minSpecial": { - "message": "Minimum special", + "message": "न्यूनतम विषेश अक्षरहरू", "description": "Minimum Special Characters" }, "ambiguous": { - "message": "Avoid ambiguous characters" + "message": "अस्पष्ट अक्षरहरूबाट बच्नुहोस्" }, "searchCollection": { - "message": "Search collection" + "message": "सङ्ग्रह खोज्नुहोस्" }, "searchFolder": { - "message": "Search folder" + "message": "फोल्डर खोज्नुहोस्" }, "searchFavorites": { - "message": "Search favorites" + "message": "मनपर्ने खोज्नुहोस्" }, "searchType": { "message": "Search type", @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 34f90d7c4f8..aafc348d908 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Geef de basis-URL van jouw zelfgehoste Bitwarden-installatie." }, + "selfHostedBaseUrlHint": { + "message": "Specificeer de basis-URL van je zelfgehoste Bitwarden-installatie. Bijvoorbeeld: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Voor geavanceerde instellingen, kun je de basis-URL van iedere dienst afzonderlijk instellen." + }, + "selfHostedEnvFormInvalid": { + "message": "Je moet de basisserver-URL of ten minste één aangepaste omgeving toevoegen." + }, "customEnvironment": { "message": "Aangepaste omgeving" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Uitgelogd" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Je inlogsessie is verlopen." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Wachtwoord bijgewerkt", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exporteren vanuit" + }, "exportVault": { "message": "Kluis exporteren" }, "fileFormat": { "message": "Bestandsindeling" }, + "fileEncryptedExportWarningDesc": { + "message": "We beveiligen deze bestandsexport met een wachtwoord beveiligd, je hebt het bestandswachtwoord nodig om het te decoderen." + }, + "filePassword": { + "message": "Bestandswachtwoord" + }, + "exportPasswordDescription": { + "message": "We gebruiken dit wachtwoord bij het exporteren en importeren van dit bestand" + }, + "accountRestrictedOptionDescription": { + "message": "Gebruik de encryptiesleutel van je account, afgeleid van je gebruikersnaam en hoodfwachtwoord, om de export te versleutelen en importeren te beperken tot het huidige Bitwarden-account." + }, + "passwordProtected": { + "message": "Beveiligd met wachtwoord" + }, + "passwordProtectedOptionDescription": { + "message": "Stel een bestandswachtwoord in om de export te versleutelen en te importeren naar een willekeurig Bitwarden-account met het wachtwoord voor decoderen." + }, + "exportTypeHeading": { + "message": "Exporttype" + }, + "accountRestricted": { + "message": "Account beperkt" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Bestandswachtwoord\" en \"Bestandswachtwoord bevestigen\" komen niet overeen." + }, "hCaptchaUrl": { "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Account wisselen" }, + "alreadyHaveAccount": { + "message": "Heb je al een account?" + }, "options": { "message": "Opties" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Organisatiekluis exporteren" + }, + "exportingOrganizationVaultDesc": { + "message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items of items van andere organisaties.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Vergrendeld" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Inloggen verzocht" }, + "creatingAccountOn": { + "message": "Account maken bij" + }, + "checkYourEmail": { + "message": "Check je e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Volg de link in de e-mail die verstuurd is naar" + }, + "andContinueCreatingYourAccount": { + "message": "en ga verder met het aanmaken van je account." + }, + "noEmail": { + "message": "Geen e-mail?" + }, + "goBack": { + "message": "Ga terug" + }, + "toEditYourEmailAddress": { + "message": "om je e-mailadres te bewerken." + }, "exposedMasterPassword": { "message": "Gelekt hoofdwachtwoord" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Belangrijk:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Je hoofdwachtwoord kan niet hersteld worden als je het vergeet!" }, diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 6054b114c15..e383a118310 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logga ut" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Innloggingsøkta di har gått ut." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Hjelp" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha-nettadresse", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 4b072a68897..8f2f11bc30f 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index 6e624f28475..6acefb31b80 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Wpisz podstawowy adres URL hostowanej instalacji Bitwarden." }, + "selfHostedBaseUrlHint": { + "message": "Określ bazowy adres URL swojej instalacji Bitwarden. Przykład: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Dla zaawansowanych konfiguracji możesz określić podstawowy adres URL niezależnie dla każdej usługi." + }, + "selfHostedEnvFormInvalid": { + "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." + }, "customEnvironment": { "message": "Niestandardowe środowisko" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Wylogowano" }, + "loggedOutDesc": { + "message": "Zostałeś wylogowany z konta." + }, "loginExpired": { "message": "Twoja sesja wygasła." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Błąd podczas odświeżania tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nie znaleziono tokenu odświeżającego ani kluczy API. Spróbuj wylogować się i zalogować ponownie." + }, "help": { "message": "Pomoc" }, @@ -1299,12 +1317,42 @@ "message": "Hasło zostało zaktualizowane", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Eksportuj z" + }, "exportVault": { "message": "Eksportuj sejf" }, "fileFormat": { "message": "Format pliku" }, + "fileEncryptedExportWarningDesc": { + "message": "Plik będzie chroniony hasłem, które będzie wymagane do odszyfrowania pliku." + }, + "filePassword": { + "message": "Hasło do pliku" + }, + "exportPasswordDescription": { + "message": "Hasło będzie używane do eksportowania i importowania pliku" + }, + "accountRestrictedOptionDescription": { + "message": "Użyj klucza szyfrowania konta, pochodzącego z nazwy użytkownika konta i hasła głównego, aby zaszyfrować eksport i ograniczyć import tylko do bieżącego konta Bitwarden." + }, + "passwordProtected": { + "message": "Chroniona hasłem" + }, + "passwordProtectedOptionDescription": { + "message": "Ustaw hasło dla pliku, aby zaszyfrować eksport i zaimportować je na dowolne konto Bitwarden przy użyciu hasła do odszyfrowania." + }, + "exportTypeHeading": { + "message": "Rodzaj eksportu" + }, + "accountRestricted": { + "message": "Konto ograniczone" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“Hasło pliku” i “Potwierdź hasło pliku“ nie pasują do siebie." + }, "hCaptchaUrl": { "message": "Adres URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Przełącz konto" }, + "alreadyHaveAccount": { + "message": "Masz już konto?" + }, "options": { "message": "Opcje" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Eksportowanie sejfu organizacji" + }, + "exportingOrganizationVaultDesc": { + "message": "Tylko sejf organizacji powiązany z $ORGANIZATION$ zostanie wyeksportowany. Pozycje w poszczególnych sejfach lub innych organizacji nie będą uwzględnione.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Zablokowany" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Wysłano prośbę logowania" }, + "creatingAccountOn": { + "message": "Tworzenie konta na" + }, + "checkYourEmail": { + "message": "Sprawdź swoją pocztę e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kliknij łącze w wiadomości e-mail wysłanej do" + }, + "andContinueCreatingYourAccount": { + "message": "i kontynuuj tworzenie konta." + }, + "noEmail": { + "message": "Brak wiadomości e-mail?" + }, + "goBack": { + "message": "Wróć" + }, + "toEditYourEmailAddress": { + "message": "aby edytować swój adres e-mail." + }, "exposedMasterPassword": { "message": "Ujawnione hasło główne" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Ważne:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Zostałeś wylogowany, ponieważ token dostępu nie mógł zostać odszyfrowany. Zaloguj się ponownie, aby rozwiązać ten problem." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Zostałeś wylogowany, ponieważ Twój token odświeżania nie mógł zostać pobrany. Zaloguj się ponownie, aby rozwiązać ten problem." + }, "masterPasswordHint": { "message": "Twoje hasło główne nie może zostać odzyskane, jeśli je zapomnisz!" }, diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index ae58f54dd9c..b9e051de18e 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifique a URL de base da sua instalação local do Bitwarden." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Ambiente Personalizado" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Sessão encerrada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Ajuda" }, @@ -1299,12 +1317,42 @@ "message": "Senha atualizada", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Exportar cofre" }, "fileFormat": { "message": "Formato do arquivo" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Trocar de Conta" }, + "alreadyHaveAccount": { + "message": "Já tem uma conta?" + }, "options": { "message": "Opções" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Bloqueado" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Acesso solicitado" }, + "creatingAccountOn": { + "message": "Criando conta em" + }, + "checkYourEmail": { + "message": "Verifique seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue criando a sua conta." + }, + "noEmail": { + "message": "Sem e-mail?" + }, + "goBack": { + "message": "Voltar" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "exposedMasterPassword": { "message": "Senha Mestra comprometida" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Importante:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "A sua senha mestra não pode ser recuperada se você esquecê-la!" }, @@ -2824,7 +2914,7 @@ "message": "Erro ao atribuir pasta de destino." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizar itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Voltar para $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Voltar", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Remover $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index d57fc481d8c..a1fc775bdef 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local." }, + "selfHostedBaseUrlHint": { + "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local. Exemplo: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Para uma configuração avançada, pode especificar o URL de base de cada serviço de forma independente." + }, + "selfHostedEnvFormInvalid": { + "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -708,7 +717,7 @@ "message": "URL do servidor da API" }, "webVaultUrl": { - "message": "URL do servidor do cofre web" + "message": "URL do servidor do cofre Web" }, "identityUrl": { "message": "URL do servidor de identidade" @@ -743,6 +752,9 @@ "loggedOut": { "message": "Sessão terminada" }, + "loggedOutDesc": { + "message": "Foi terminada a sessão da sua conta." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Erro no acesso ao token de atualização" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Não foi encontrado nenhum token de atualização ou chaves API. Por favor, tente terminar a sessão e voltar a iniciá-la." + }, "help": { "message": "Ajuda" }, @@ -1299,12 +1317,42 @@ "message": "Palavra-passe atualizada a", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exportar de" + }, "exportVault": { "message": "Exportar cofre" }, "fileFormat": { "message": "Formato do ficheiro" }, + "fileEncryptedExportWarningDesc": { + "message": "A exportação deste ficheiro será protegida por uma palavra-passe e exigirá a palavra-passe do ficheiro para ser desencriptada." + }, + "filePassword": { + "message": "Palavra-passe do ficheiro" + }, + "exportPasswordDescription": { + "message": "Esta palavra-passe será utilizada para exportar e importar este ficheiro" + }, + "accountRestrictedOptionDescription": { + "message": "Utilize a chave de encriptação da sua conta, derivada do nome de utilizador e da palavra-passe mestra da sua conta, para encriptar a exportação e restringir a importação apenas à conta Bitwarden atual." + }, + "passwordProtected": { + "message": "Protegido por palavra-passe" + }, + "passwordProtectedOptionDescription": { + "message": "Defina uma palavra-passe do ficheiro para encriptar a exportação e importe-a para qualquer conta Bitwarden utilizando a palavra-passe de desencriptação." + }, + "exportTypeHeading": { + "message": "Tipo de exportação" + }, + "accountRestricted": { + "message": "Conta restringida" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Palavra-passe do ficheiro\" e \"Confirmar palavra-passe do ficheiro\" não correspondem." + }, "hCaptchaUrl": { "message": "URL do hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Mudar de conta" }, + "alreadyHaveAccount": { + "message": "Já tem uma conta?" + }, "options": { "message": "Opções" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "A exportar o cofre da organização" + }, + "exportingOrganizationVaultDesc": { + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Os itens em cofres individuais ou noutras organizações não serão incluídos.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Bloqueado" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Pedido de início de sessão" }, + "creatingAccountOn": { + "message": "A criar uma conta em" + }, + "checkYourEmail": { + "message": "Verifique o seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue a criação da sua conta." + }, + "noEmail": { + "message": "Não recebeu o e-mail?" + }, + "goBack": { + "message": "Volte atrás" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "exposedMasterPassword": { "message": "Palavra-passe mestra exposta" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Importante:" }, + "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." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "A sessão foi encerrada porque o seu token de atualização não pôde ser recuperado. Por favor, inicie sessão novamente para resolver este problema." + }, "masterPasswordHint": { "message": "A sua palavra-passe mestra não pode ser recuperada se a esquecer!" }, @@ -2824,7 +2914,7 @@ "message": "Erro ao atribuir a pasta de destino." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Ver itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Voltar a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Voltar", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Remover $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 0a538593adb..65800596c86 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specificați URL-ul de bază al implementări Bitwarden găzduită local." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Mediu personalizat" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Deconectat" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Ajutor" }, @@ -1299,12 +1317,42 @@ "message": "Parolă actualizată", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export de seif" }, "fileFormat": { "message": "Format de fișier" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "Url-ul hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Comutare cont" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Opțiuni" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Blocat" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 75f30af8931..2392d902289 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Укажите URL Bitwarden на вашем сервере." }, + "selfHostedBaseUrlHint": { + "message": "Укажите базовый URL вашего локального хостинга Bitwarden. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для продвинутой конфигурации можно указать базовый URL каждой службы отдельно." + }, + "selfHostedEnvFormInvalid": { + "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." + }, "customEnvironment": { "message": "Пользовательское окружение" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Вы вышли из хранилища" }, + "loggedOutDesc": { + "message": "Вы вышли из своего аккаунта." + }, "loginExpired": { "message": "Истек срок действия вашего сеанса." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Ошибка обновления токена доступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не найдены токен обновления или ключи API. Пожалуйста, попробуйте выполнить выход и повторно авторизоваться." + }, "help": { "message": "Помощь" }, @@ -1299,12 +1317,42 @@ "message": "Пароль обновлен", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Экспорт из" + }, "exportVault": { "message": "Экспорт хранилища" }, "fileFormat": { "message": "Формат файла" }, + "fileEncryptedExportWarningDesc": { + "message": "Экспорт этого файла будет защищен паролем, и для расшифровки потребуется пароль файла." + }, + "filePassword": { + "message": "Пароль к файлу" + }, + "exportPasswordDescription": { + "message": "Этот пароль будет использоваться для экспорта и импорта этого файла" + }, + "accountRestrictedOptionDescription": { + "message": "Использовать ключ шифрования вашего аккаунта, полученный из имени пользователя и мастер-пароля, для шифрования экспорта и ограничения импорта только для текущего аккаунта Bitwarden." + }, + "passwordProtected": { + "message": "Пароль защищен" + }, + "passwordProtectedOptionDescription": { + "message": "Установите пароль файла для шифрования экспорта и импортируйте его в любую учетную запись Bitwarden, используя пароль для расшифровки." + }, + "exportTypeHeading": { + "message": "Тип экспорта" + }, + "accountRestricted": { + "message": "Ограничено аккаунтом" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Пароль к файлу\" и \"Подтверждение пароля к файлу\" не совпадают." + }, "hCaptchaUrl": { "message": "URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Сменить аккаунт" }, + "alreadyHaveAccount": { + "message": "Уже зарегистрированы?" + }, "options": { "message": "Опции" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Экспорт хранилища организации" + }, + "exportingOrganizationVaultDesc": { + "message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Элементы из личных хранилищ и из других организаций включены не будут.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Заблокировано" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Запрошено разрешение на вход" }, + "creatingAccountOn": { + "message": "Создание аккаунта" + }, + "checkYourEmail": { + "message": "Проверьте свою почту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдите по ссылке из отправленного письма" + }, + "andContinueCreatingYourAccount": { + "message": "и продолжите создание аккаунта." + }, + "noEmail": { + "message": "Нет письма?" + }, + "goBack": { + "message": "Вернуться" + }, + "toEditYourEmailAddress": { + "message": "для изменения адреса email." + }, "exposedMasterPassword": { "message": "Мастер-пароль скомпрометирован" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Важно:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Вы были деавторизованы, поскольку ваш токен доступа не удалось расшифровать. Чтобы решить эту проблему, авторизуйтесь снова." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Вы были деавторизованы, поскольку ваш токен обновления не удалось получить. Чтобы решить эту проблему, авторизуйтесь снова." + }, "masterPasswordHint": { "message": "Ваш мастер-пароль невозможно восстановить, если вы его забудете!" }, diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index ab9f837c2f9..1d518c4374d 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 22e744f502e..dc6aa65d84f 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Zadajte URL Bitwarden inštalácie, ktorú prevádzkujete vo vlastnom prostredí." }, + "selfHostedBaseUrlHint": { + "message": "Zadajte základnú URL adresu lokálne hosťovanej inštalácie Bitwarden. Napríklad: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Pre rozšírenú konfiguráciu môžete zadať základnú adresu URL každej služby nezávisle." + }, + "selfHostedEnvFormInvalid": { + "message": "Musíte pridať buď základnú adresu URL servera, alebo aspoň jedno vlastné prostredie." + }, "customEnvironment": { "message": "Vlastné prostredie" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odhlásený" }, + "loggedOutDesc": { + "message": "Boli ste odhlásení zo svojho účtu." + }, "loginExpired": { "message": "Platnosť prihlásenia vypršala." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Chyba obnovenia prístupového tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nenašiel sa žiadny token obnovenia ani kľúče API. Skúste sa odhlásiť a znova prihlásiť." + }, "help": { "message": "Nápoveď" }, @@ -1299,12 +1317,42 @@ "message": "Heslo bolo aktualizované", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exportovať z" + }, "exportVault": { "message": "Export trezoru" }, "fileFormat": { "message": "Formát Súboru" }, + "fileEncryptedExportWarningDesc": { + "message": "Tento exportovaný súbor bude chránený heslom a na dešifrovanie bude potrebné heslo súboru." + }, + "filePassword": { + "message": "Heslo súboru" + }, + "exportPasswordDescription": { + "message": "Toto heslo sa použije na export a import tohto súboru" + }, + "accountRestrictedOptionDescription": { + "message": "Na zašifrovanie exportu a obmedzenie importu len na aktuálny účet Bitwarden použite šifrovací kľúč účtu odvodený z používateľského mena a hlavného hesla účtu." + }, + "passwordProtected": { + "message": "Chránené heslom" + }, + "passwordProtectedOptionDescription": { + "message": "Nastavte heslo súboru na zašifrovanie exportu a importujte ho do akéhokoľvek účtu Bitwarden pomocou hesla na dešifrovanie." + }, + "exportTypeHeading": { + "message": "Typ exportu" + }, + "accountRestricted": { + "message": "Obmedzený účet" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "\"Heslo súboru\" a \"Potvrdiť heslo súboru\" sa nezhodujú." + }, "hCaptchaUrl": { "message": "URL hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Prepnúť účet" }, + "alreadyHaveAccount": { + "message": "Už máte účet?" + }, "options": { "message": "Možnosti" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exportovanie trezora organizácie" + }, + "exportingOrganizationVaultDesc": { + "message": "Exportované budú iba položky trezora organizácie spojené s $ORGANIZATION$. Položky osobného trezora a položky z iných organizácií nebudú zahrnuté.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Zamknutý" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Požadované prihlásenie" }, + "creatingAccountOn": { + "message": "Vytváranie účtu na" + }, + "checkYourEmail": { + "message": "Skontrolujte si svoj e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Postupujte podľa odkazu v e-maile zaslanom na adresu" + }, + "andContinueCreatingYourAccount": { + "message": "a pokračujte vo vytváraní účtu." + }, + "noEmail": { + "message": "Žiadny email?" + }, + "goBack": { + "message": "Prejsť späť" + }, + "toEditYourEmailAddress": { + "message": "na úpravu e-mailovej adresy." + }, "exposedMasterPassword": { "message": "Odhalené hlavné heslo" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Dôležité:" }, + "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." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Boli ste odhlásení, pretože váš prístupový token nebolo možné načítať. Na vyriešenie tohto problému sa znova prihláste." + }, "masterPasswordHint": { "message": "Vaše hlavné heslo sa nebude dať obnoviť, ak ho zabudnete!" }, @@ -2824,7 +2914,7 @@ "message": "Chyba pri priraďovaní cieľového priečinka." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobraziť položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Späť do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Späť", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odstrániť $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 57f9dbf36a1..86cb28289a9 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Okolje po meri" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Vaša seja je potekla." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Pomoč" }, @@ -1299,12 +1317,42 @@ "message": "Geslo je bilo posodobljeno", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Izvoz trezorja" }, "fileFormat": { "message": "Format datoteke" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 144c8e7bb69..90b6e7292f0 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Наведите основни УРЛ ваше локалне Bitwarden инсталације." }, + "selfHostedBaseUrlHint": { + "message": "Наведите основну УРЛ адресу вашег локалног хостовања Bitwarden-а. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За напредну конфигурацију, можете навести основну УРЛ адресу сваке услуге независно." + }, + "selfHostedEnvFormInvalid": { + "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." + }, "customEnvironment": { "message": "Прилагођено окружење" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Одјављено" }, + "loggedOutDesc": { + "message": "Одјављени сте са свог налога." + }, "loginExpired": { "message": "Ваша сесија је истекла." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Грешка при освежавању токена приступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Није пронађен токен за освежавање или АПИ кључеви. Покушајте да се одјавите и поново пријавите." + }, "help": { "message": "Помоћ" }, @@ -1299,12 +1317,42 @@ "message": "Лозинка ажурирана", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Извоз од" + }, "exportVault": { "message": "Извоз сефа" }, "fileFormat": { "message": "Формат датотеке" }, + "fileEncryptedExportWarningDesc": { + "message": "Овај извоз ће бити заштићен лозинком и захтеваће лозинку датотеке за дешифровање." + }, + "filePassword": { + "message": "Лозинка датотеке" + }, + "exportPasswordDescription": { + "message": "Ова лозинка ће се користити за извоз и увоз ове датотеке" + }, + "accountRestrictedOptionDescription": { + "message": "Користите кључ за шифровање вашег налога, изведен из корисничког имена и главне лозинке, да шифрујете извоз и ограничите увоз само на тренутни Bitwarden налог." + }, + "passwordProtected": { + "message": "Заштићено лозинком" + }, + "passwordProtectedOptionDescription": { + "message": "Подесите лозинку за шифровање извоза и увоз у било који Bitwarden налог користећи лозинку за дешифровање." + }, + "exportTypeHeading": { + "message": "Тип извоза" + }, + "accountRestricted": { + "message": "Налог је ограничен" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Унете лозинке се не подударају." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Промени налог" }, + "alreadyHaveAccount": { + "message": "Већ имате налог?" + }, "options": { "message": "Опције" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Извоз сефа организације" + }, + "exportingOrganizationVaultDesc": { + "message": "Биће извезен само сеф организације повезан са $ORGANIZATION$. Ставке у појединачним сефовима или другим организацијама неће бити укључене.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Закључано" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Захтев пријаве" }, + "creatingAccountOn": { + "message": "Креирај налог на" + }, + "checkYourEmail": { + "message": "Проверите свој имејл" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Пратите везу послатој на" + }, + "andContinueCreatingYourAccount": { + "message": "и наставите са креирањем налога." + }, + "noEmail": { + "message": "Немате имејл?" + }, + "goBack": { + "message": "Ићи назад" + }, + "toEditYourEmailAddress": { + "message": "да измените свој имејл." + }, "exposedMasterPassword": { "message": "Изложена главна лозинка" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Важно:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Одјављени сте јер ваш токен за приступ није могао да се дешифрује. Пријавите се поново да бисте решили овај проблем." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Одјављени сте јер ваш токен за освежавање није могао да се преузме. Пријавите се поново да бисте решили овај проблем." + }, "masterPasswordHint": { "message": "Ваша главна лозинка се не може повратити ако је заборавите!" }, @@ -2824,7 +2914,7 @@ "message": "Грешка при додељивању циљне фасцикле." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Видети ставке у $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад на $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index dfebac7e4ed..6d078a34e87 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Ange bas-URL:en för din \"on-premises\"-hostade Bitwarden-installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Anpassad miljö" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Utloggad" }, + "loggedOutDesc": { + "message": "Du har blivit utloggad från ditt konto." + }, "loginExpired": { "message": "Din inloggningssession har löpt ut." }, @@ -801,10 +813,10 @@ "message": "Ändra huvudlösenord" }, "continueToWebApp": { - "message": "Continue to web app?" + "message": "Fortsätt till webapp?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "You can change your master password on the Bitwarden web app." + "message": "Du kan ändra ditt huvudlösenord i Bitwardens webbapp." }, "fingerprintPhrase": { "message": "Fingeravtrycksfras", @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Hjälp" }, @@ -1299,12 +1317,42 @@ "message": "Lösenordet uppdaterades", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Exportera från" + }, "exportVault": { "message": "Exportera valv" }, "fileFormat": { "message": "Filformat" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha-URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -1633,10 +1681,10 @@ "message": "Webbläsarintegration stöds inte" }, "browserIntegrationErrorTitle": { - "message": "Error enabling browser integration" + "message": "Fel vid aktivering av webbläsarintegration" }, "browserIntegrationErrorDesc": { - "message": "An error has occurred while enabling browser integration." + "message": "Ett fel uppstod vid aktivering av webbläsarintegration." }, "browserIntegrationMasOnlyDesc": { "message": "Tyvärr stöds webbläsarintegration för tillfället endast i versionen från Mac App Store." @@ -1654,10 +1702,10 @@ "message": "Aktivera ett ytterligare säkerhetslager genom att kräva validering av fingeravtrycksfrasen när du upprättar en länk mellan skrivbordet och webbläsaren. När det här är aktiverat krävs ingripande och verifiering från användaren varje gång en anslutning etableras." }, "enableHardwareAcceleration": { - "message": "Use hardware acceleration" + "message": "Använd hårdvaruacceleration" }, "enableHardwareAccelerationDesc": { - "message": "By default this setting is ON. Turn OFF only if you experience graphical issues. Restart is required." + "message": "Som standard är den här inställningen påslagen. Stäng endast av om du upplever grafiska problem. Omstart krävs." }, "approve": { "message": "Godkänn" @@ -1699,7 +1747,7 @@ "message": "En organisationspolicy påverkar dina ägarskapsalternativ." }, "personalOwnershipPolicyInEffectImports": { - "message": "An organization policy has blocked importing items into your individual vault." + "message": "En organisationspolicy har blockerat importering av objekt till ditt personliga valv." }, "allSends": { "message": "Alla Sends", @@ -1953,7 +2001,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": "Dina organisationspolicys påverkar ditt valvs timeout. Maximal tillåten tid innan timeout är $HOURS$ timme(ar) och $MINUTES$ minut(er). Ditt valvs timeout-åtgärd är angivet som $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -1970,7 +2018,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "Your organization policies have set your vault timeout action to $ACTION$.", + "message": "Dina organisationspolicys har satt din timeout-åtgärd till $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Byt konto" }, + "alreadyHaveAccount": { + "message": "Har du redan ett konto?" + }, "options": { "message": "Alternativ" }, @@ -2060,7 +2111,7 @@ "message": "Exporterar individuellt valv" }, "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": "Endast de personliga valvobjekten som är associerade med $EMAIL$ kommer att exporteras. Organisationers valv kommer inte att inkluderas. Endast information om valv kommer att exporteras och kommer inte att innehålla tillhörande bilagor.", "placeholders": { "email": { "content": "$1", @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Låst" }, @@ -2147,11 +2210,11 @@ } }, "forwarderGeneratedBy": { - "message": "Generated by Bitwarden.", + "message": "Genererad av Bitwarden.", "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": { @@ -2161,7 +2224,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Ogiltig $SERVICENAME$ API-token", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2171,7 +2234,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Ogiltig $SERVICENAME$ API token: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2185,7 +2248,7 @@ } }, "forwarderNoAccountId": { - "message": "Unable to obtain $SERVICENAME$ masked email account ID.", + "message": "Gick inte att få $SERVICENAME$ maskerat e-postkonto-ID.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2215,7 +2278,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Okänt $SERVICENAME$ fel uppstod.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2225,7 +2288,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Okänd vidarebefordrare: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Inloggning begärd" }, + "creatingAccountOn": { + "message": "Skapa konto på" + }, + "checkYourEmail": { + "message": "Kolla din e-post" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Följ länken i e-postmeddelandet som skickats till" + }, + "andContinueCreatingYourAccount": { + "message": "och fortsätt att skapa ditt konto." + }, + "noEmail": { + "message": "Ingen e-post?" + }, + "goBack": { + "message": "Gå tillbaka" + }, + "toEditYourEmailAddress": { + "message": "för att redigera din e-postadress." + }, "exposedMasterPassword": { "message": "Huvudlösenordet har exponerats" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Viktigt:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Ditt huvudlösenord kan inte återställas om du glömmer det!" }, @@ -2421,19 +2511,19 @@ } }, "windowsBiometricUpdateWarning": { - "message": "Bitwarden recommends updating your biometric settings to require your master password (or PIN) on the first unlock. Would you like to update your settings now?" + "message": "Bitwarden rekommenderar att du uppdaterar dina biometriska inställningar för att kräva ditt huvudlösenord (eller PIN) på den första upplåsningen. Vill du uppdatera dina inställningar nu?" }, "windowsBiometricUpdateWarningTitle": { - "message": "Recommended Settings Update" + "message": "Rekommenderade inställningsuppdateringar" }, "deviceApprovalRequired": { - "message": "Device approval required. Select an approval option below:" + "message": "Godkännande av enhet krävs. Välj ett godkännandealternativ nedan:" }, "rememberThisDevice": { "message": "Kom ihåg denna enhet" }, "uncheckIfPublicDevice": { - "message": "Uncheck if using a public device" + "message": "Avmarkera om du använder en offentlig enhet" }, "approveFromYourOtherDevice": { "message": "Godkänn från din andra enhet" @@ -2461,7 +2551,7 @@ "message": "självhostad" }, "accessDenied": { - "message": "Access denied. You do not have permission to view this page." + "message": "Åtkomst nekad. Du har inte behörighet att se denna sida." }, "accountSuccessfullyCreated": { "message": "Ditt konto har skapats!" @@ -2506,7 +2596,7 @@ } }, "inputMaxLength": { - "message": "Input must not exceed $COUNT$ characters in length.", + "message": "Inmatningen får inte överstiga $COUNT$ tecken i längd.", "placeholders": { "count": { "content": "$1", @@ -2524,7 +2614,7 @@ } }, "inputMinValue": { - "message": "Input value must be at least $MIN$.", + "message": "Inmatningsvärdet måste vara minst $MIN$.", "placeholders": { "min": { "content": "$1", @@ -2533,7 +2623,7 @@ } }, "inputMaxValue": { - "message": "Input value must not exceed $MAX$.", + "message": "Inmatningsvärdet får inte överstiga $MAX$.", "placeholders": { "max": { "content": "$1", @@ -2545,7 +2635,7 @@ "message": "En eller flera e-postadresser är ogiltiga" }, "inputTrimValidator": { - "message": "Input must not contain only whitespace.", + "message": "Indata får inte bara innehålla blanksteg.", "description": "Notification to inform the user that a form's input can't contain only whitespace." }, "inputEmail": { @@ -2676,7 +2766,7 @@ "message": "Välj en samling" }, "importTargetHint": { - "message": "Select this option if you want the imported file contents moved to a $DESTINATION$", + "message": "Välj det här alternativet om du vill att innehållet i den importerade filen ska flyttas till en $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": { @@ -2723,7 +2813,7 @@ "message": "Bekräfta fillösenord" }, "multifactorAuthenticationCancelled": { - "message": "Multifactor authentication cancelled" + "message": "Flerfaktorsautentisering avbruten" }, "noLastPassDataFound": { "message": "Ingen LastPass-data hittades" @@ -2741,7 +2831,7 @@ "message": "Felaktig PIN-kod" }, "multifactorAuthenticationFailed": { - "message": "Multifactor authentication failed" + "message": "Flerfaktorsautentisering misslyckades" }, "includeSharedFolders": { "message": "Inkludera delade mappar" @@ -2753,13 +2843,13 @@ "message": "Importerar ditt konto..." }, "lastPassMFARequired": { - "message": "LastPass multifactor authentication required" + "message": "LastPass flerfaktorsautentisering krävs" }, "lastPassMFADesc": { "message": "Ange din engångskod från din autentiseringsapp" }, "lastPassOOBDesc": { - "message": "Approve the login request in your authentication app or enter a one-time passcode." + "message": "Godkänn inloggningsbegäran i din autentiseringsapp eller ange en engångskod." }, "passcode": { "message": "Kod" @@ -2800,16 +2890,16 @@ "description": "Label indicating the most common import formats" }, "success": { - "message": "Success" + "message": "Lyckades" }, "troubleshooting": { "message": "Felsökning" }, "disableHardwareAccelerationRestart": { - "message": "Disable hardware acceleration and restart" + "message": "Inaktivera hårdvaruacceleration och starta om" }, "enableHardwareAccelerationRestart": { - "message": "Enable hardware acceleration and restart" + "message": "Inaktivera hårdvaruacceleration och starta om" }, "removePasskey": { "message": "Ta bort nyckel" @@ -2818,13 +2908,13 @@ "message": "Nyckel borttagen" }, "errorAssigningTargetCollection": { - "message": "Error assigning target collection." + "message": "Fel vid tilldelning av målsamling." }, "errorAssigningTargetFolder": { - "message": "Error assigning target folder." + "message": "Fel vid tilldelning av målmapp." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visa objekt i $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Tillbaka till $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,11 @@ } }, "back": { - "message": "Back", + "message": "Tillbaka", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Ta bort $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index d8cb372359c..4d0d96038f6 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premises hosted Bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Help" }, @@ -1299,12 +1317,42 @@ "message": "Password updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export vault" }, "fileFormat": { "message": "File format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index a89bd7518fb..827bbc8b867 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Specify the base URL of your on-premise hosted bitwarden installation." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "ออกจากระบบแล้ว" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "เซสชันของคุณหมดอายุแล้ว" }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "ช่วยเหลือ" }, @@ -1299,12 +1317,42 @@ "message": "Password Updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export Vault" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha Url", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Switch account" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Options" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Locked" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 73d2b6dc07d..8d3d061a58e 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Kurum içi barındırılan Bitwarden kurulumunuzun taban URL'sini belirtin." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Özel ortam" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Çıkış yapıldı" }, + "loggedOutDesc": { + "message": "Hesabınızdan çıkış yapıldı." + }, "loginExpired": { "message": "Oturumunuzun süresi doldu." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Yardım" }, @@ -1299,12 +1317,42 @@ "message": "Parola güncelleme", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Kasayı dışa aktar" }, "fileFormat": { "message": "Dosya biçimi" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha adresi", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Hesabı değiştir" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Seçenekler" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Kilitli" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Giriş istendi" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Açığa Çıkmış Ana Parola" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Önemli:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Ana parolanızı unutursanız kurtaramazsınız!" }, diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index dbc0911430d..0ac31300741 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -693,7 +693,16 @@ "message": "Середовище власного хостингу" }, "selfHostedEnvironmentFooter": { - "message": "Вкажіть основну URL-адресу Bitwarden, встановленого на локальному хостингу." + "message": "Вкажіть основну URL-адресу локально розміщеного встановлення Bitwarden." + }, + "selfHostedBaseUrlHint": { + "message": "Вкажіть основну URL-адресу вашого локально розміщеного встановлення Bitwarden. Зразок: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для розширеної конфігурації ви можете вказати основну URL-адресу окремо для кожної служби." + }, + "selfHostedEnvFormInvalid": { + "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, "customEnvironment": { "message": "Власне середовище" @@ -743,6 +752,9 @@ "loggedOut": { "message": "Ви вийшли" }, + "loggedOutDesc": { + "message": "Ви вийшли з облікового запису." + }, "loginExpired": { "message": "Тривалість вашого сеансу завершилась." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Помилка оновлення токена доступу" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не знайдено токен оновлення або ключі API. Спробуйте вийти, а потім увійти знову." + }, "help": { "message": "Допомога" }, @@ -1299,12 +1317,42 @@ "message": "Пароль оновлено", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Експортувати з" + }, "exportVault": { "message": "Експортувати сховище" }, "fileFormat": { "message": "Формат файлу" }, + "fileEncryptedExportWarningDesc": { + "message": "Цей експортований файл буде захищений паролем, який необхідно ввести для його розшифрування." + }, + "filePassword": { + "message": "Пароль файлу" + }, + "exportPasswordDescription": { + "message": "Цей пароль буде використано для експортування та імпортування цього файлу" + }, + "accountRestrictedOptionDescription": { + "message": "Використовуйте ключ шифрування свого облікового запису, створений на основі імені користувача й головного пароля, щоб зашифрувати експортовані дані та обмежити можливість імпортування лише до поточного облікового запису Bitwarden." + }, + "passwordProtected": { + "message": "Захищено паролем" + }, + "passwordProtectedOptionDescription": { + "message": "Встановіть пароль файлу, щоб зашифрувати експортовані дані та імпортувати до будь-якого облікового запису Bitwarden за допомогою цього пароля." + }, + "exportTypeHeading": { + "message": "Тип експорту" + }, + "accountRestricted": { + "message": "Обмежено обліковим записом" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "Пароль файлу та підтвердження пароля відрізняються." + }, "hCaptchaUrl": { "message": "URL-адреса hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Перемкнути обліковий запис" }, + "alreadyHaveAccount": { + "message": "Вже маєте обліковий запис?" + }, "options": { "message": "Налаштування" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Експортування сховища організації" + }, + "exportingOrganizationVaultDesc": { + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Елементи особистих сховищ або інших організацій не будуть включені.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Заблоковано" }, @@ -2133,7 +2196,7 @@ "message": "Згенеруйте псевдонім е-пошти зі стороннім сервісом пересилання." }, "forwarderError": { - "message": "$SERVICENAME$ error: $ERRORMESSAGE$", + "message": "Помилка $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2147,11 +2210,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": { @@ -2161,7 +2224,7 @@ } }, "forwaderInvalidToken": { - "message": "Invalid $SERVICENAME$ API token", + "message": "Недійсний токен API для $SERVICENAME$", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2171,7 +2234,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Invalid $SERVICENAME$ API token: $ERRORMESSAGE$", + "message": "Недійсний токен API для $SERVICENAME$: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2185,7 +2248,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": { @@ -2195,7 +2258,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": { @@ -2205,7 +2268,7 @@ } }, "forwarderNoUrl": { - "message": "Invalid $SERVICENAME$ url.", + "message": "Недійсна URL-адреса для $SERVICENAME$.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2215,7 +2278,7 @@ } }, "forwarderUnknownError": { - "message": "Unknown $SERVICENAME$ error occurred.", + "message": "Сталася невідома помилка $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2225,7 +2288,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Unknown forwarder: '$SERVICENAME$'.", + "message": "Невідомий засіб переспрямування: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Виконано запит входу" }, + "creatingAccountOn": { + "message": "Створення облікового запису" + }, + "checkYourEmail": { + "message": "Перевірте свою електронну пошту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдіть за посиланням у листі, надісланому на" + }, + "andContinueCreatingYourAccount": { + "message": "і завершіть створення облікового запису." + }, + "noEmail": { + "message": "Не отримали електронного листа?" + }, + "goBack": { + "message": "Поверніться назад" + }, + "toEditYourEmailAddress": { + "message": "і виправте адресу електронної пошти." + }, "exposedMasterPassword": { "message": "Головний пароль викрито" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Важливо:" }, + "accessTokenUnableToBeDecrypted": { + "message": "Ви вийшли з системи, оскільки неможливо розшифрувати ваш токен доступу. Увійдіть знову, щоб виправити цю проблему." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "Ви вийшли з системи, оскільки неможливо отримати ваш токен оновлення. Увійдіть знову, щоб виправити цю проблему." + }, "masterPasswordHint": { "message": "Головний пароль неможливо відновити, якщо ви його втратите!" }, @@ -2824,7 +2914,7 @@ "message": "Помилка призначення цільової теки." }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Переглянути записи в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад до $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index bea273740f6..70a920b8587 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "Chỉ định liên kết cơ bản của cài đặt Bitwarden tại chỗ của bạn." }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "Môi trường tùy chỉnh" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "Đăng xuất" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "Trợ giúp" }, @@ -1299,12 +1317,42 @@ "message": "Password Updated", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "Export Vault" }, "fileFormat": { "message": "File Format" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "Url hCaptcha", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "Chuyển tài khoản" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "Tùy chọn" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "Đã khóa" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "Log in requested" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "Exposed Master Password" }, @@ -2408,6 +2492,12 @@ "important": { "message": "Important:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "Your master password cannot be recovered if you forget it!" }, diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 71993f6e7bd..8a80875bbf3 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "指定您本地托管的 Bitwarden 安装的基础 URL。" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "自定义环境" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "已注销" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登录会话已过期。" }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "帮助" }, @@ -1299,12 +1317,42 @@ "message": "密码更新于", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "导出自" + }, "exportVault": { "message": "导出密码库" }, "fileFormat": { "message": "文件格式" }, + "fileEncryptedExportWarningDesc": { + "message": "此文件导出将受密码保护,需要文件密码才能解密。" + }, + "filePassword": { + "message": "文件密码" + }, + "exportPasswordDescription": { + "message": "此密码将用于导出和导入此文件" + }, + "accountRestrictedOptionDescription": { + "message": "使用衍生自您账户的用户名和主密码的账户加密密钥,以加密此导出并限制只能导入到当前的 Bitwarden 账户。" + }, + "passwordProtected": { + "message": "密码保护" + }, + "passwordProtectedOptionDescription": { + "message": "设置一个文件密码用来加密此导出,并使用此密码解密以导入到任意 Bitwarden 账户。" + }, + "exportTypeHeading": { + "message": "导出类型" + }, + "accountRestricted": { + "message": "账户受限" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "「文件密码」与「确认文件密码」不一致。" + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "切换账户" }, + "alreadyHaveAccount": { + "message": "已经拥有账户了吗?" + }, "options": { "message": "选项" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "正在导出组织密码库" + }, + "exportingOrganizationVaultDesc": { + "message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库数据。不包括个人密码库和其他组织中的项目。", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "已锁定" }, @@ -2269,7 +2332,7 @@ "message": "记住电子邮件地址" }, "notYou": { - "message": "不是你?" + "message": "不是您吗?" }, "newAroundHere": { "message": "初来乍到吗?" @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "已请求登录" }, + "creatingAccountOn": { + "message": "创建账户于" + }, + "checkYourEmail": { + "message": "检查您的电子邮箱" + }, + "followTheLinkInTheEmailSentTo": { + "message": "点击发送到电子邮件中的链接" + }, + "andContinueCreatingYourAccount": { + "message": "然后继续创建您的账户。" + }, + "noEmail": { + "message": "没收到电子邮件吗?" + }, + "goBack": { + "message": "返回" + }, + "toEditYourEmailAddress": { + "message": "编辑您的电子邮件地址。" + }, "exposedMasterPassword": { "message": "已暴露的主密码" }, @@ -2408,6 +2492,12 @@ "important": { "message": "重要事项:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "主密码忘记后,将无法恢复!" }, @@ -2787,7 +2877,7 @@ "message": "从 CSV 导入" }, "lastPassTryAgainCheckEmail": { - "message": "请重试或查找来自 LastPass 的电子邮件以验证您的身份。" + "message": "请重试或查找来自 LastPass 的可以验证您的身份的电子邮件。" }, "collection": { "message": "集合" @@ -2824,7 +2914,7 @@ "message": "分配目标文件夹时出错。" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "查看 $NAME$ 中的项目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -2834,7 +2924,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "返回 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -2844,11 +2934,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": { diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 373875628a4..47bcaae70e8 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -695,6 +695,15 @@ "selfHostedEnvironmentFooter": { "message": "指定您本地托管的 Bitwarden 安裝之基礎 URL。" }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, "customEnvironment": { "message": "自訂環境" }, @@ -743,6 +752,9 @@ "loggedOut": { "message": "已登出" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登入會話已過期。" }, @@ -1212,6 +1224,12 @@ } } }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "help": { "message": "說明" }, @@ -1299,12 +1317,42 @@ "message": "密碼更新於", "description": "ex. Date this password was updated" }, + "exportFrom": { + "message": "Export from" + }, "exportVault": { "message": "匯出密碼庫" }, "fileFormat": { "message": "檔案格式" }, + "fileEncryptedExportWarningDesc": { + "message": "This file export will be password protected and require the file password to decrypt." + }, + "filePassword": { + "message": "File password" + }, + "exportPasswordDescription": { + "message": "This password will be used to export and import this file" + }, + "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." + }, + "passwordProtected": { + "message": "Password protected" + }, + "passwordProtectedOptionDescription": { + "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" + }, + "accountRestricted": { + "message": "Account restricted" + }, + "filePasswordAndConfirmFilePasswordDoNotMatch": { + "message": "“File password” and “Confirm file password“ do not match." + }, "hCaptchaUrl": { "message": "hCaptcha URL", "description": "hCaptcha is the name of a website, should not be translated" @@ -2050,6 +2098,9 @@ "switchAccount": { "message": "切換帳戶" }, + "alreadyHaveAccount": { + "message": "Already have an account?" + }, "options": { "message": "選項" }, @@ -2068,6 +2119,18 @@ } } }, + "exportingOrganizationVaultTitle": { + "message": "Exporting organization vault" + }, + "exportingOrganizationVaultDesc": { + "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "placeholders": { + "organization": { + "content": "$1", + "example": "ACME Moving Co." + } + } + }, "locked": { "message": "已鎖定" }, @@ -2390,6 +2453,27 @@ "logInRequested": { "message": "已要求登入" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "exposedMasterPassword": { "message": "已暴露的主密碼" }, @@ -2408,6 +2492,12 @@ "important": { "message": "重要:" }, + "accessTokenUnableToBeDecrypted": { + "message": "You have been logged out because your access token could not be decrypted. Please log in again to resolve this issue." + }, + "refreshTokenSecureStorageRetrievalFailure": { + "message": "You have been logged out because your refresh token could not be retrieved. Please log in again to resolve this issue." + }, "masterPasswordHint": { "message": "如果您忘記主密碼,沒有復原的方法!" }, diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 63d6e062a1e..738f053de73 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -3,22 +3,13 @@ import * as path from "path"; import { app } from "electron"; import { Subject, firstValueFrom } from "rxjs"; -import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service"; -import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { ClientType } from "@bitwarden/common/enums"; -import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; -import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { DefaultBiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; -import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- For dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; -import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state"; -import { EncryptServiceImplementation } from "@bitwarden/common/platform/services/cryptography/encrypt.service.implementation"; import { DefaultEnvironmentService } from "@bitwarden/common/platform/services/default-environment.service"; -import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; @@ -41,18 +32,13 @@ 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 { Account } from "./models/account"; import { BiometricsService, BiometricsServiceAbstraction } from "./platform/main/biometric/index"; 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 { DesktopSettingsService } from "./platform/services/desktop-settings.service"; import { ElectronLogMainService } from "./platform/services/electron-log.main.service"; -import { ELECTRON_SUPPORTS_SECURE_STORAGE } from "./platform/services/electron-platform-utils.service"; -import { ElectronStateService } from "./platform/services/electron-state.service"; import { ElectronStorageService } from "./platform/services/electron-storage.service"; import { I18nMainService } from "./platform/services/i18n.main.service"; -import { IllegalSecureStorageService } from "./platform/services/illegal-secure-storage.service"; import { ElectronMainMessagingService } from "./services/electron-main-messaging.service"; import { isMacAppStore } from "./utils"; @@ -63,15 +49,10 @@ export class Main { memoryStorageService: MemoryStorageService; memoryStorageForStateProviders: MemoryStorageServiceForStateProviders; messagingService: MessageSender; - stateService: StateService; environmentService: DefaultEnvironmentService; - mainCryptoFunctionService: MainCryptoFunctionService; desktopCredentialStorageListener: DesktopCredentialStorageListener; desktopSettingsService: DesktopSettingsService; migrationRunner: MigrationRunner; - tokenService: TokenServiceAbstraction; - keyGenerationService: KeyGenerationServiceAbstraction; - encryptService: EncryptService; windowMain: WindowMain; messagingMain: MessagingMain; @@ -160,30 +141,6 @@ export class Main { this.environmentService = new DefaultEnvironmentService(stateProvider, accountService); - this.mainCryptoFunctionService = new MainCryptoFunctionService(); - this.mainCryptoFunctionService.init(); - - this.keyGenerationService = new KeyGenerationService(this.mainCryptoFunctionService); - - this.encryptService = new EncryptServiceImplementation( - this.mainCryptoFunctionService, - this.logService, - true, // log mac failures - ); - - // Note: secure storage service is not available and should not be called in the main background process. - const illegalSecureStorageService = new IllegalSecureStorageService(); - - this.tokenService = new TokenService( - singleUserStateProvider, - globalStateProvider, - ELECTRON_SUPPORTS_SECURE_STORAGE, - illegalSecureStorageService, - this.keyGenerationService, - this.encryptService, - this.logService, - ); - this.migrationRunner = new MigrationRunner( this.storageService, this.logService, @@ -191,27 +148,10 @@ export class Main { ClientType.Desktop, ); - // TODO: this state service will have access to on disk storage, but not in memory storage. - // If we could get this to work using the stateService singleton that the rest of the app uses we could save - // ourselves from some hacks, like having to manually update the app menu vs. the menu subscribing to events. - this.stateService = new ElectronStateService( - this.storageService, - null, - this.memoryStorageService, - this.logService, - new StateFactory(GlobalState, Account), - accountService, // will not broadcast logouts. This is a hack until we can remove messaging dependency - this.environmentService, - this.tokenService, - this.migrationRunner, - ); - this.desktopSettingsService = new DesktopSettingsService(stateProvider); - const biometricStateService = new DefaultBiometricStateService(stateProvider); this.windowMain = new WindowMain( - this.stateService, biometricStateService, this.logService, this.storageService, @@ -219,18 +159,24 @@ export class Main { (arg) => this.processDeepLink(arg), (win) => this.trayMain.setupWindowListeners(win), ); - this.messagingMain = new MessagingMain(this, this.stateService, this.desktopSettingsService); + 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>(); + const messageSubject = new Subject>>(); this.messagingService = MessageSender.combine( new SubjectMessageSender(messageSubject), // For local messages new ElectronMainMessagingService(this.windowMain), ); messageSubject.asObservable().subscribe((message) => { - this.messagingMain.onMessage(message); + void this.messagingMain.onMessage(message).catch((err) => { + this.logService.error( + "Error while handling message", + message?.command ?? "Unknown command", + err, + ); + }); }); this.powerMonitorMain = new PowerMonitorMain(this.messagingService); @@ -298,7 +244,7 @@ export class Main { await this.updaterMain.init(); const [browserIntegrationEnabled, ddgIntegrationEnabled] = await Promise.all([ - this.stateService.getEnableBrowserIntegration(), + firstValueFrom(this.desktopSettingsService.browserIntegrationEnabled$), firstValueFrom(this.desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$), ]); diff --git a/apps/desktop/src/main/messaging.main.ts b/apps/desktop/src/main/messaging.main.ts index a9f80b7d207..68b1597ac45 100644 --- a/apps/desktop/src/main/messaging.main.ts +++ b/apps/desktop/src/main/messaging.main.ts @@ -2,8 +2,7 @@ import * as fs from "fs"; import * as path from "path"; import { app, ipcMain } from "electron"; - -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { firstValueFrom } from "rxjs"; import { Main } from "../main"; import { DesktopSettingsService } from "../platform/services/desktop-settings.service"; @@ -17,7 +16,6 @@ export class MessagingMain { constructor( private main: Main, - private stateService: StateService, private desktopSettingsService: DesktopSettingsService, ) {} @@ -29,10 +27,13 @@ export class MessagingMain { const loginSettings = app.getLoginItemSettings(); await this.desktopSettingsService.setOpenAtLogin(loginSettings.openAtLogin); } - ipcMain.on("messagingService", async (event: any, message: any) => this.onMessage(message)); + ipcMain.on( + "messagingService", + async (event: any, message: any) => await this.onMessage(message), + ); } - onMessage(message: any) { + async onMessage(message: any) { switch (message.command) { case "scheduleNextSync": this.scheduleNextSync(); @@ -44,13 +45,14 @@ export class MessagingMain { this.updateTrayMenu(message.updateRequest); break; case "minimizeOnCopy": - // 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.getMinimizeOnCopyToClipboard().then((shouldMinimize) => { - if (shouldMinimize && this.main.windowMain.win !== null) { + { + const shouldMinimizeOnCopy = await firstValueFrom( + this.desktopSettingsService.minimizeOnCopy$, + ); + if (shouldMinimizeOnCopy && this.main.windowMain.win !== null) { this.main.windowMain.win.minimize(); } - }); + } break; case "showTray": this.main.trayMain.showTray(); diff --git a/apps/desktop/src/main/tray.main.ts b/apps/desktop/src/main/tray.main.ts index 948c48f519a..8450a653222 100644 --- a/apps/desktop/src/main/tray.main.ts +++ b/apps/desktop/src/main/tray.main.ts @@ -140,7 +140,7 @@ export class TrayMain { } updateContextMenu() { - if (this.contextMenu != null && this.isLinux()) { + if (this.tray != null && this.contextMenu != null && this.isLinux()) { this.tray.setContextMenu(this.contextMenu); } } diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index 64b4bc48d28..e82d16ee9fd 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -6,7 +6,6 @@ import { app, BrowserWindow, ipcMain, nativeTheme, screen, session } from "elect import { firstValueFrom } from "rxjs"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; @@ -38,7 +37,6 @@ export class WindowMain { readonly defaultHeight = 600; constructor( - private stateService: StateService, private biometricStateService: BiometricStateService, private logService: LogService, private storageService: AbstractStorageService, diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 508c42fa720..34a4dc99f65 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.5.0", + "version": "2024.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.5.0", + "version": "2024.6.0", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-native": "file:../desktop_native", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index ea4b95491cb..3a629f37cb0 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.5.0", + "version": "2024.6.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/services/desktop-settings.service.ts b/apps/desktop/src/platform/services/desktop-settings.service.ts index 09ddad07c1b..ff29ce50a0f 100644 --- a/apps/desktop/src/platform/services/desktop-settings.service.ts +++ b/apps/desktop/src/platform/services/desktop-settings.service.ts @@ -4,7 +4,9 @@ import { DESKTOP_SETTINGS_DISK, KeyDefinition, StateProvider, + UserKeyDefinition, } from "@bitwarden/common/platform/state"; +import { UserId } from "@bitwarden/common/types/guid"; import { WindowState } from "../models/domain/window-state"; @@ -48,6 +50,27 @@ const ALWAYS_ON_TOP_KEY = new KeyDefinition(DESKTOP_SETTINGS_DISK, "alw deserializer: (b) => b, }); +const BROWSER_INTEGRATION_ENABLED = new KeyDefinition( + DESKTOP_SETTINGS_DISK, + "browserIntegrationEnabled", + { + deserializer: (b) => b, + }, +); + +const BROWSER_INTEGRATION_FINGERPRINT_ENABLED = new KeyDefinition( + DESKTOP_SETTINGS_DISK, + "browserIntegrationFingerprintEnabled", + { + deserializer: (b) => b, + }, +); + +const MINIMIZE_ON_COPY = new UserKeyDefinition(DESKTOP_SETTINGS_DISK, "minimizeOnCopy", { + deserializer: (b) => b, + clearOn: [], // User setting, no need to clear +}); + /** * Various settings for controlling application behavior specific to the desktop client. */ @@ -61,41 +84,68 @@ export class DesktopSettingsService { /** * Tha applications setting for whether or not to close the application into the system tray. */ - closeToTray$ = this.closeToTrayState.state$.pipe(map((value) => value ?? false)); + closeToTray$ = this.closeToTrayState.state$.pipe(map(Boolean)); private readonly minimizeToTrayState = this.stateProvider.getGlobal(MINIMIZE_TO_TRAY_KEY); /** * The application setting for whether or not to minimize the applicaiton into the system tray. */ - minimizeToTray$ = this.minimizeToTrayState.state$.pipe(map((value) => value ?? false)); + minimizeToTray$ = this.minimizeToTrayState.state$.pipe(map(Boolean)); private readonly startToTrayState = this.stateProvider.getGlobal(START_TO_TRAY_KEY); /** * The application setting for whether or not to start the application into the system tray. */ - startToTray$ = this.startToTrayState.state$.pipe(map((value) => value ?? false)); + startToTray$ = this.startToTrayState.state$.pipe(map(Boolean)); private readonly trayEnabledState = this.stateProvider.getGlobal(TRAY_ENABLED_KEY); /** * Whether or not the system tray has been enabled. */ - trayEnabled$ = this.trayEnabledState.state$.pipe(map((value) => value ?? false)); + trayEnabled$ = this.trayEnabledState.state$.pipe(map(Boolean)); private readonly openAtLoginState = this.stateProvider.getGlobal(OPEN_AT_LOGIN_KEY); /** * The application setting for whether or not the application should open at system login. */ - openAtLogin$ = this.openAtLoginState.state$.pipe(map((value) => value ?? false)); + openAtLogin$ = this.openAtLoginState.state$.pipe(map(Boolean)); private readonly alwaysShowDockState = this.stateProvider.getGlobal(ALWAYS_SHOW_DOCK_KEY); /** * The application setting for whether or not the application should show up in the dock. */ - alwaysShowDock$ = this.alwaysShowDockState.state$.pipe(map((value) => value ?? false)); + alwaysShowDock$ = this.alwaysShowDockState.state$.pipe(map(Boolean)); private readonly alwaysOnTopState = this.stateProvider.getGlobal(ALWAYS_ON_TOP_KEY); - alwaysOnTop$ = this.alwaysOnTopState.state$.pipe(map((value) => value ?? false)); + alwaysOnTop$ = this.alwaysOnTopState.state$.pipe(map(Boolean)); + + private readonly browserIntegrationEnabledState = this.stateProvider.getGlobal( + BROWSER_INTEGRATION_ENABLED, + ); + + /** + * The application setting for whether or not the browser integration is enabled. + */ + browserIntegrationEnabled$ = this.browserIntegrationEnabledState.state$.pipe(map(Boolean)); + + private readonly browserIntegrationFingerprintEnabledState = this.stateProvider.getGlobal( + BROWSER_INTEGRATION_FINGERPRINT_ENABLED, + ); + + /** + * The application setting for whether or not the fingerprint should be verified before browser communication. + */ + browserIntegrationFingerprintEnabled$ = + this.browserIntegrationFingerprintEnabledState.state$.pipe(map(Boolean)); + + private readonly minimizeOnCopyState = this.stateProvider.getActive(MINIMIZE_ON_COPY); + + /** + * The active users setting for whether or not the application should minimize itself + * when a value is copied to the clipboard. + */ + minimizeOnCopy$ = this.minimizeOnCopyState.state$.pipe(map(Boolean)); constructor(private stateProvider: StateProvider) { this.window$ = this.windowState.state$.pipe( @@ -177,4 +227,32 @@ export class DesktopSettingsService { async setAlwaysOnTop(value: boolean) { await this.alwaysOnTopState.update(() => value); } + + /** + * Sets a setting for whether or not the browser integration has been enabled. + * @param value `true` if the integration with the browser extension is enabled, + * `false` if it is not. + */ + async setBrowserIntegrationEnabled(value: boolean) { + await this.browserIntegrationEnabledState.update(() => value); + } + + /** + * Sets a setting for whether or not the browser fingerprint should be verified before + * communication with the browser integration should be done. + * @param value `true` if the fingerprint should be validated before use, `false` if it should not. + */ + async setBrowserIntegrationFingerprintEnabled(value: boolean) { + await this.browserIntegrationFingerprintEnabledState.update(() => value); + } + + /** + * Sets the minimize on copy value for the current user. + * @param value `true` if the application should minimize when a value is copied, + * `false` if it should not. + * @param userId The user id of the user to update the setting for. + */ + async setMinimizeOnCopy(value: boolean, userId: UserId) { + await this.stateProvider.getUser(userId, MINIMIZE_ON_COPY).update(() => value); + } } diff --git a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts index 037c303b3b6..109a52a8d8f 100644 --- a/apps/desktop/src/platform/services/electron-renderer-message.sender.ts +++ b/apps/desktop/src/platform/services/electron-renderer-message.sender.ts @@ -2,9 +2,9 @@ import { MessageSender, CommandDefinition } from "@bitwarden/common/platform/mes import { getCommand } from "@bitwarden/common/platform/messaging/internal"; export class ElectronRendererMessageSender implements MessageSender { - send( + send>( commandDefinition: CommandDefinition | string, - payload: object | T = {}, + payload: Record | T = {}, ): void { const command = getCommand(commandDefinition); ipc.platform.sendMessage(Object.assign({}, { command: command }, payload)); diff --git a/apps/desktop/src/platform/utils/from-ipc-messaging.ts b/apps/desktop/src/platform/utils/from-ipc-messaging.ts index 254a215ceb3..cdefbf5c506 100644 --- a/apps/desktop/src/platform/utils/from-ipc-messaging.ts +++ b/apps/desktop/src/platform/utils/from-ipc-messaging.ts @@ -8,8 +8,8 @@ import { tagAsExternal } from "@bitwarden/common/platform/messaging/internal"; * @returns An observable stream of messages. */ export const fromIpcMessaging = () => { - return fromEventPattern>( + return fromEventPattern>>( (handler) => ipc.platform.onMessage.addListener(handler), (handler) => ipc.platform.onMessage.removeListener(handler), - ).pipe(tagAsExternal, share()); + ).pipe(tagAsExternal(), share()); }; diff --git a/apps/desktop/src/services/electron-main-messaging.service.ts b/apps/desktop/src/services/electron-main-messaging.service.ts index ce4ffd903a8..150890bf56f 100644 --- a/apps/desktop/src/services/electron-main-messaging.service.ts +++ b/apps/desktop/src/services/electron-main-messaging.service.ts @@ -87,7 +87,10 @@ export class ElectronMainMessagingService implements MessageSender { }); } - send(commandDefinition: CommandDefinition | string, arg: T | object = {}) { + send>( + commandDefinition: CommandDefinition | string, + arg: T | Record = {}, + ) { const command = getCommand(commandDefinition); const message = Object.assign({}, { command: command }, arg); if (this.windowMain.win != null) { diff --git a/apps/desktop/src/services/native-messaging.service.ts b/apps/desktop/src/services/native-messaging.service.ts index 9abd3b635ad..8dc0a57c99c 100644 --- a/apps/desktop/src/services/native-messaging.service.ts +++ b/apps/desktop/src/services/native-messaging.service.ts @@ -10,7 +10,6 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se 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 { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -23,6 +22,7 @@ import { BrowserSyncVerificationDialogComponent } from "../app/components/browse 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"; @@ -40,7 +40,7 @@ export class NativeMessagingService { private platformUtilService: PlatformUtilsService, private logService: LogService, private messagingService: MessagingService, - private stateService: StateService, + private desktopSettingService: DesktopSettingsService, private biometricStateService: BiometricStateService, private nativeMessageHandler: NativeMessageHandlerService, private dialogService: DialogService, @@ -78,7 +78,7 @@ export class NativeMessagingService { return; } - if (await this.stateService.getEnableBrowserIntegrationFingerprint()) { + if (await firstValueFrom(this.desktopSettingService.browserIntegrationFingerprintEnabled$)) { ipc.platform.nativeMessaging.sendMessage({ command: "verifyFingerprint", appId: appId, diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 208bbc70f03..37992ecea0e 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -23,7 +23,7 @@ import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broa import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index a62d494f294..ad8112db485 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -17,6 +17,9 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], + "@bitwarden/generator-extensions": ["../../libs/tools/generator/extensions/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], @@ -25,6 +28,7 @@ "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/node/*": ["../../libs/node/src/*"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"] }, "useDefineForClassFields": false diff --git a/apps/desktop/webpack.renderer.js b/apps/desktop/webpack.renderer.js index 1ebeadef055..dc3cdf1fef5 100644 --- a/apps/desktop/webpack.renderer.js +++ b/apps/desktop/webpack.renderer.js @@ -24,8 +24,7 @@ const common = { { loader: "babel-loader", options: { - configFile: false, - plugins: ["@angular/compiler-cli/linker/babel"], + configFile: "../../babel.config.json", }, }, ], diff --git a/apps/web/package.json b/apps/web/package.json index 6e5355c7086..286811dd5c6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.5.0", + "version": "2024.6.0", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index e14bb62a35d..12c051271e1 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -97,19 +97,15 @@ export abstract class BaseEventsComponent { this.loading = true; let events: EventView[] = []; let promise: Promise; - try { - promise = this.loadAndParseEvents( - dates[0], - dates[1], - clearExisting ? null : this.continuationToken, - ); + promise = this.loadAndParseEvents( + dates[0], + dates[1], + clearExisting ? null : this.continuationToken, + ); - const result = await promise; - this.continuationToken = result.continuationToken; - events = result.events; - } catch (e) { - this.logService.error(`Handled exception: ${e}`); - } + const result = await promise; + this.continuationToken = result.continuationToken; + events = result.events; if (!clearExisting && this.events != null && this.events.length > 0) { this.events = this.events.concat(events); diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index d1a48a78e11..237e2c6e30c 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -52,7 +52,7 @@ *ngIf="canShowBillingTab(organization)" > - + diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 47ca0998bbc..4383656bee1 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; -import { map, mergeMap, Observable, Subject, takeUntil } from "rxjs"; +import { combineLatest, map, mergeMap, Observable, Subject, switchMap, takeUntil } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -16,7 +16,8 @@ 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 { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { PolicyType, ProviderStatusType } 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"; @@ -55,9 +56,14 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { organization$: Observable; showPaymentAndHistory$: Observable; hideNewOrgButton$: Observable; + organizationIsUnmanaged$: Observable; private _destroy = new Subject(); + protected consolidatedBillingEnabled$ = this.configService.getFeatureFlag$( + FeatureFlag.EnableConsolidatedBilling, + ); + protected showPaymentMethodWarningBanners$ = this.configService.getFeatureFlag$( FeatureFlag.ShowPaymentMethodWarningBanners, ); @@ -68,6 +74,7 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private configService: ConfigService, private policyService: PolicyService, + private providerService: ProviderService, ) {} async ngOnInit() { @@ -94,6 +101,24 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy { ); this.hideNewOrgButton$ = this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg); + + const provider$ = this.organization$.pipe( + switchMap((organization) => this.providerService.get$(organization.providerId)), + ); + + this.organizationIsUnmanaged$ = combineLatest([ + this.consolidatedBillingEnabled$, + this.organization$, + provider$, + ]).pipe( + map( + ([consolidatedBillingEnabled, organization, provider]) => + !consolidatedBillingEnabled || + !organization.hasProvider || + !provider || + provider.providerStatus !== ProviderStatusType.Billable, + ), + ); } ngOnDestroy() { diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html index df731c5cf16..166467ada09 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html @@ -52,7 +52,7 @@

{{ "editGroupCollectionsDesc" | i18n }} - + {{ "restrictedCollectionAssignmentDesc" | i18n }}

diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index a02c4a8da95..38ef0025349 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -196,12 +196,17 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { }), ); - protected canEditAnyCollection$ = combineLatest([ + protected canAssignAccessToAnyCollection$ = combineLatest([ this.organization$, this.flexibleCollectionsV1Enabled$, + this.allowAdminAccessToAllCollectionItems$, ]).pipe( - map(([org, flexibleCollectionsV1Enabled]) => - org.canEditAnyCollection(flexibleCollectionsV1Enabled), + map( + ([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) => + org.canEditAnyCollection(flexibleCollectionsV1Enabled) || + // Manage Groups custom permission cannot edit any collection but they can assign access from this dialog + // if permitted by collection management settings + (org.permissions.manageGroups && allowAdminAccessToAllCollectionItems), ), ); diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index 35e28b5239e..2a092e26100 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -144,6 +144,7 @@ {{ "cannotAddYourselfToCollections" | i18n }}
-
- {{ "userPermissionOverrideHelperDesc" | i18n }} - +
+ + {{ "userPermissionOverrideHelperDesc" | i18n }} + + {{ "restrictedCollectionAssignmentDesc" | i18n }}
diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index 8309d4d7253..8482cbb4dee 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -107,7 +107,7 @@ export class MemberDialogComponent implements OnDestroy { protected allowAdminAccessToAllCollectionItems$: Observable; protected restrictEditingSelf$: Observable; - protected canEditAnyCollection$: Observable; + protected canAssignAccessToAnyCollection$: Observable; protected permissionsGroup = this.formBuilder.group({ manageAssignedCollectionsGroup: this.formBuilder.group>({ @@ -222,12 +222,17 @@ export class MemberDialogComponent implements OnDestroy { FeatureFlag.FlexibleCollectionsV1, ); - this.canEditAnyCollection$ = combineLatest([ + this.canAssignAccessToAnyCollection$ = combineLatest([ this.organization$, flexibleCollectionsV1Enabled$, + this.allowAdminAccessToAllCollectionItems$, ]).pipe( - map(([org, flexibleCollectionsV1Enabled]) => - org.canEditAnyCollection(flexibleCollectionsV1Enabled), + map( + ([org, flexibleCollectionsV1Enabled, allowAdminAccessToAllCollectionItems]) => + org.canEditAnyCollection(flexibleCollectionsV1Enabled) || + // Manage Users custom permission cannot edit any collection but they can assign access from this dialog + // if permitted by collection management settings + (org.permissions.manageUsers && allowAdminAccessToAllCollectionItems), ), ); diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html index 063a8759e6f..69b81ae5571 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.html @@ -1,7 +1,5 @@
- - {{ "editPolicy" | i18n }} - {{ policy.name | i18n }} - +
{ - comp.keyType = "organization"; - comp.entityId = this.organizationId; - comp.postKey = this.organizationApiService.getOrCreateApiKey.bind( - this.organizationApiService, - ); - comp.scope = "api.organization"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "apiKeyWarning"; - comp.apiKeyDescription = "apiKeyDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "organization", + entityId: this.organizationId, + postKey: this.organizationApiService.getOrCreateApiKey.bind(this.organizationApiService), + scope: "api.organization", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "apiKeyWarning", + apiKeyDescription: "apiKeyDesc", + }, }); } async rotateApiKey() { - await this.modalService.openViewRef(ApiKeyComponent, this.rotateApiKeyModalRef, (comp) => { - comp.keyType = "organization"; - comp.isRotation = true; - comp.entityId = this.organizationId; - comp.postKey = this.organizationApiService.rotateApiKey.bind(this.organizationApiService); - comp.scope = "api.organization"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "apiKeyWarning"; - comp.apiKeyDescription = "apiKeyRotateDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "organization", + isRotation: true, + entityId: this.organizationId, + postKey: this.organizationApiService.rotateApiKey.bind(this.organizationApiService), + scope: "api.organization", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "apiKeyWarning", + apiKeyDescription: "apiKeyRotateDesc", + }, }); } } diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 6c71309243e..c9fbf359f0f 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -14,6 +14,7 @@ import { timer, } from "rxjs"; +import { LogoutReason } from "@bitwarden/auth/common"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; @@ -34,13 +35,13 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { DialogService, ToastOptions, ToastService } from "@bitwarden/components"; import { PolicyListService } from "./admin-console/core/policy-list.service"; import { @@ -148,7 +149,7 @@ export class AppComponent implements OnDestroy, OnInit { this.router.navigate(["/"]); break; case "logout": - await this.logOut(!!message.expired, message.redirect); + await this.logOut(message.logoutReason, message.redirect); break; case "lockVault": await this.vaultTimeoutService.lock(); @@ -278,7 +279,34 @@ export class AppComponent implements OnDestroy, OnInit { this.destroy$.complete(); } - private async logOut(expired: boolean, redirect = true) { + private async displayLogoutReason(logoutReason: LogoutReason) { + let toastOptions: ToastOptions; + switch (logoutReason) { + case "invalidSecurityStamp": + case "sessionExpired": { + toastOptions = { + variant: "warning", + title: this.i18nService.t("loggedOut"), + message: this.i18nService.t("loginExpired"), + }; + break; + } + default: { + toastOptions = { + variant: "info", + title: this.i18nService.t("loggedOut"), + message: this.i18nService.t("loggedOutDesc"), + }; + break; + } + } + + this.toastService.showToast(toastOptions); + } + + private async logOut(logoutReason: LogoutReason, redirect = true) { + await this.displayLogoutReason(logoutReason); + await this.eventUploadService.uploadEvents(); const userId = (await this.stateService.getUserId()) as UserId; @@ -308,14 +336,6 @@ export class AppComponent implements OnDestroy, OnInit { await this.searchService.clearIndex(); this.authService.logOut(async () => { - if (expired) { - this.platformUtilsService.showToast( - "warning", - this.i18nService.t("loggedOut"), - this.i18nService.t("loginExpired"), - ); - } - await this.stateService.clean({ userId: userId }); await this.accountService.clean(userId); diff --git a/apps/web/src/app/auth/accept-organization.component.ts b/apps/web/src/app/auth/accept-organization.component.ts deleted file mode 100644 index 52e3b644940..00000000000 --- a/apps/web/src/app/auth/accept-organization.component.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { Component } from "@angular/core"; -import { ActivatedRoute, Params, Router } from "@angular/router"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { - OrganizationUserAcceptInitRequest, - OrganizationUserAcceptRequest, -} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; -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 { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; -import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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 { OrgKey } from "@bitwarden/common/types/key"; - -import { BaseAcceptComponent } from "../common/base.accept.component"; - -@Component({ - selector: "app-accept-organization", - templateUrl: "accept-organization.component.html", -}) -export class AcceptOrganizationComponent extends BaseAcceptComponent { - orgName: string; - - protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"]; - - constructor( - router: Router, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - route: ActivatedRoute, - stateService: StateService, - private cryptoService: CryptoService, - private policyApiService: PolicyApiServiceAbstraction, - private policyService: PolicyService, - private logService: LogService, - private organizationApiService: OrganizationApiServiceAbstraction, - private organizationUserService: OrganizationUserService, - private messagingService: MessagingService, - private apiService: ApiService, - ) { - super(router, platformUtilsService, i18nService, route, stateService); - } - - async authedHandler(qParams: Params): Promise { - const initOrganization = - qParams.initOrganization != null && qParams.initOrganization.toLocaleLowerCase() === "true"; - if (initOrganization) { - this.actionPromise = this.acceptInitOrganizationFlow(qParams); - } else { - const needsReAuth = (await this.stateService.getOrganizationInvitation()) == null; - if (needsReAuth) { - // Accepting an org invite requires authentication from a logged out state - this.messagingService.send("logout", { redirect: false }); - await this.prepareOrganizationInvitation(qParams); - return; - } - - // User has already logged in and passed the Master Password policy check - this.actionPromise = this.acceptFlow(qParams); - } - - await this.actionPromise; - await this.apiService.refreshIdentityToken(); - await this.stateService.setOrganizationInvitation(null); - this.platformUtilService.showToast( - "success", - this.i18nService.t("inviteAccepted"), - initOrganization - ? this.i18nService.t("inviteInitAcceptedDesc") - : this.i18nService.t("inviteAcceptedDesc"), - { timeout: 10000 }, - ); - // 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(["/vault"]); - } - - async unauthedHandler(qParams: Params): Promise { - await this.prepareOrganizationInvitation(qParams); - - // 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. - await this.accelerateInviteAcceptIfPossible(qParams); - } - - private async acceptInitOrganizationFlow(qParams: Params): Promise { - return this.prepareAcceptInitRequest(qParams).then((request) => - this.organizationUserService.postOrganizationUserAcceptInit( - qParams.organizationId, - qParams.organizationUserId, - request, - ), - ); - } - - private async acceptFlow(qParams: Params): Promise { - return this.prepareAcceptRequest(qParams).then((request) => - this.organizationUserService.postOrganizationUserAccept( - qParams.organizationId, - qParams.organizationUserId, - request, - ), - ); - } - - private async prepareAcceptInitRequest( - qParams: Params, - ): Promise { - const request = new OrganizationUserAcceptInitRequest(); - request.token = qParams.token; - - const [encryptedOrgKey, orgKey] = await this.cryptoService.makeOrgKey(); - const [orgPublicKey, encryptedOrgPrivateKey] = await this.cryptoService.makeKeyPair(orgKey); - const collection = await this.cryptoService.encrypt( - this.i18nService.t("defaultCollection"), - orgKey, - ); - - request.key = encryptedOrgKey.encryptedString; - request.keys = new OrganizationKeysRequest( - orgPublicKey, - encryptedOrgPrivateKey.encryptedString, - ); - request.collectionName = collection.encryptedString; - - return request; - } - - private async prepareAcceptRequest(qParams: Params): Promise { - const request = new OrganizationUserAcceptRequest(); - request.token = qParams.token; - - if (await this.performResetPasswordAutoEnroll(qParams)) { - const response = await this.organizationApiService.getKeys(qParams.organizationId); - - if (response == null) { - throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); - } - - const publicKey = Utils.fromB64ToArray(response.publicKey); - - // RSA Encrypt user's encKey.key with organization public key - const userKey = await this.cryptoService.getUserKey(); - const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey); - - // Add reset password key to accept request - request.resetPasswordKey = encryptedKey.encryptedString; - } - return request; - } - - private async performResetPasswordAutoEnroll(qParams: Params): Promise { - let policyList: Policy[] = null; - try { - const policies = await this.policyApiService.getPoliciesByToken( - qParams.organizationId, - qParams.token, - qParams.email, - qParams.organizationUserId, - ); - policyList = Policy.fromListResponse(policies); - } catch (e) { - this.logService.error(e); - } - - if (policyList != null) { - const result = this.policyService.getResetPasswordPolicyOptions( - policyList, - qParams.organizationId, - ); - // Return true if policy enabled and auto-enroll enabled - return result[1] && result[0].autoEnrollEnabled; - } - - return false; - } - - private async prepareOrganizationInvitation(qParams: Params): Promise { - this.orgName = qParams.organizationName; - if (this.orgName != null) { - // Fix URL encoding of space issue with Angular - this.orgName = this.orgName.replace(/\+/g, " "); - } - await this.stateService.setOrganizationInvitation(qParams); - } - - private async accelerateInviteAcceptIfPossible(qParams: Params): Promise { - // Extract the query params we need to make routing acceleration decisions - const orgSsoIdentifier = qParams.orgSsoIdentifier; - const orgUserHasExistingUser = this.stringToNullOrBool(qParams.orgUserHasExistingUser); - - // if orgUserHasExistingUser is null, short circuit for backwards compatibility w/ older servers - if (orgUserHasExistingUser == null) { - return; - } - - // if user exists, send user to login - if (orgUserHasExistingUser) { - // 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(["/login"], { - queryParams: { email: qParams.email }, - }); - return; - } - - // no user exists; so either sign in via SSO and JIT provision one or simply register. - - if (orgSsoIdentifier) { - // We only send sso org identifier if the org has SSO enabled and the SSO policy required. - // 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(["/sso"], { - queryParams: { email: qParams.email, identifier: orgSsoIdentifier }, - }); - return; - } - - // if SSO is disabled OR if sso is enabled but the SSO login required policy is not enabled - // then send user to create account - // 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(["/register"], { - queryParams: { email: qParams.email, fromOrgInvite: true }, - }); - return; - } - - private stringToNullOrBool(s: string | undefined): boolean | null { - if (s === undefined) { - return null; - } - return s.toLowerCase() === "true"; - } -} diff --git a/apps/web/src/app/auth/auth.module.ts b/apps/web/src/app/auth/auth.module.ts index 056b9f161f9..6aa671558a0 100644 --- a/apps/web/src/app/auth/auth.module.ts +++ b/apps/web/src/app/auth/auth.module.ts @@ -1,9 +1,10 @@ import { NgModule } from "@angular/core"; +import { AcceptOrganizationInviteModule } from "./organization-invite/accept-organization.module"; import { AuthSettingsModule } from "./settings/settings.module"; @NgModule({ - imports: [AuthSettingsModule], + imports: [AuthSettingsModule, AcceptOrganizationInviteModule], declarations: [], providers: [], exports: [AuthSettingsModule], diff --git a/apps/web/src/app/auth/core/services/webauthn-login/request/webauthn-login-attestation-response.request.ts b/apps/web/src/app/auth/core/services/webauthn-login/request/webauthn-login-attestation-response.request.ts index ef3d657f2f9..f7c391b0ee2 100644 --- a/apps/web/src/app/auth/core/services/webauthn-login/request/webauthn-login-attestation-response.request.ts +++ b/apps/web/src/app/auth/core/services/webauthn-login/request/webauthn-login-attestation-response.request.ts @@ -20,8 +20,8 @@ export class WebauthnLoginAttestationResponseRequest extends WebauthnLoginAuthen } this.response = { - attestationObject: Utils.fromBufferToB64(credential.response.attestationObject), - clientDataJson: Utils.fromBufferToB64(credential.response.clientDataJSON), + attestationObject: Utils.fromBufferToUrlB64(credential.response.attestationObject), + clientDataJson: Utils.fromBufferToUrlB64(credential.response.clientDataJSON), }; } } diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html index 4690a4e63a5..3e1db406316 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html @@ -1,45 +1,39 @@ -
+
- -

+

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

-
-
-
-

{{ "emergencyAccess" | i18n }}

-
-
-

- {{ name }} -

-

{{ "acceptEmergencyAccess" | i18n }}

-
- -
-
-
+
+

+ {{ name }} +

+

{{ "acceptEmergencyAccess" | i18n }}

+
+
diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index 8ff847c3a22..5a92815c91f 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -1,9 +1,9 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { BaseAcceptComponent } from "../../../common/base.accept.component"; import { SharedModule } from "../../../shared"; @@ -27,10 +27,10 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, route: ActivatedRoute, - stateService: StateService, + authService: AuthService, private emergencyAccessService: EmergencyAccessService, ) { - super(router, platformUtilsService, i18nService, route, stateService); + super(router, platformUtilsService, i18nService, route, authService); } async authedHandler(qParams: Params): Promise { diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts index 792ae15690f..c3d568e1187 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts @@ -17,6 +17,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums/cipher-type"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { Folder } from "@bitwarden/common/vault/models/domain/folder"; @@ -49,6 +50,7 @@ describe("KeyRotationService", () => { let mockStateService: MockProxy; let mockConfigService: MockProxy; let mockKdfConfigService: MockProxy; + let mockSyncService: MockProxy; const mockUserId = Utils.newGuid() as UserId; const mockAccountService: FakeAccountService = mockAccountServiceWith(mockUserId); @@ -68,6 +70,7 @@ describe("KeyRotationService", () => { mockStateService = mock(); mockConfigService = mock(); mockKdfConfigService = mock(); + mockSyncService = mock(); keyRotationService = new UserKeyRotationService( mockMasterPasswordService, @@ -83,6 +86,7 @@ describe("KeyRotationService", () => { mockStateService, mockAccountService, mockKdfConfigService, + mockSyncService, ); }); diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts index 4b8d6ca1392..cac2dafd518 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.ts @@ -13,6 +13,7 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. import { UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherWithIdRequest } from "@bitwarden/common/vault/models/request/cipher-with-id.request"; import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/folder-with-id.request"; @@ -38,6 +39,7 @@ export class UserKeyRotationService { private stateService: StateService, private accountService: AccountService, private kdfConfigService: KdfConfigService, + private syncService: SyncService, ) {} /** @@ -49,6 +51,12 @@ export class UserKeyRotationService { throw new Error("Invalid master password"); } + if ((await this.syncService.getLastSync()) === null) { + throw new Error( + "The local vault is de-synced and the keys cannot be rotated. Please log out and log back in to resolve this issue.", + ); + } + // Create master key to validate the master password const masterKey = await this.cryptoService.makeMasterKey( masterPassword, 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.component.ts index 2c97bd227f9..991fe8b5971 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.component.ts @@ -1,14 +1,27 @@ -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.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", }) export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent { + protected routerService = inject(RouterService); + protected acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); + override async createUser(): Promise { try { await super.createUser(); + + // Invites from TDE orgs go through here, but the invite is + // accepted while being enrolled in admin recovery. So we need to clear + // the redirect and stored org invite. + await this.routerService.getAndClearLoginRedirectUrl(); + await this.acceptOrganizationInviteService.clearOrganizationInvitation(); + await this.router.navigate(["/vault"]); } catch (error) { this.validationService.showError(error); diff --git a/apps/web/src/app/auth/login/login.component.ts b/apps/web/src/app/auth/login/login.component.ts index 9f628b9389e..51d46f46a42 100644 --- a/apps/web/src/app/auth/login/login.component.ts +++ b/apps/web/src/app/auth/login/login.component.ts @@ -15,12 +15,10 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -32,6 +30,8 @@ import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/pass import { flagEnabled } from "../../../utils/flags"; import { RouterService, StateService } from "../../core"; +import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; +import { OrganizationInvite } from "../organization-invite/organization-invite"; @Component({ selector: "app-login", @@ -41,10 +41,11 @@ import { RouterService, StateService } from "../../core"; export class LoginComponent extends BaseLoginComponent implements OnInit { showResetPasswordAutoEnrollWarning = false; enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; - policies: ListResponse; + policies: Policy[]; showPasswordless = false; constructor( + private acceptOrganizationInviteService: AcceptOrganizationInviteService, devicesApiService: DevicesApiServiceAbstraction, appIdService: AppIdService, loginStrategyService: LoginStrategyServiceAbstraction, @@ -112,37 +113,10 @@ export class LoginComponent extends BaseLoginComponent implements OnInit { await super.ngOnInit(); }); - const invite = await this.stateService.getOrganizationInvitation(); - if (invite != null) { - let policyList: Policy[] = null; - try { - this.policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - policyList = Policy.fromListResponse(this.policies); - } catch (e) { - this.logService.error(e); - } - - if (policyList != null) { - const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( - policyList, - invite.organizationId, - ); - // Set to true if policy enabled and auto-enroll enabled - this.showResetPasswordAutoEnrollWarning = - resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; - - this.policyService - .masterPasswordPolicyOptions$(policyList) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions; - }); - } + // If there's an existing org invite, use it to get the password policies + const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite(); + if (orgInvite != null) { + await this.initPasswordPolicies(orgInvite); } } @@ -166,50 +140,69 @@ export class LoginComponent extends BaseLoginComponent implements OnInit { ) ) { const policiesData: { [id: string]: PolicyData } = {}; - this.policies.data.map((p) => (policiesData[p.id] = new PolicyData(p))); + this.policies.map((p) => (policiesData[p.id] = PolicyData.fromPolicy(p))); await this.policyService.replace(policiesData); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["update-password"]); + await this.router.navigate(["update-password"]); return; } } this.loginEmailService.clearValues(); - // 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]); + await this.router.navigate([this.successRoute]); } - goToHint() { + async goToHint() { this.setLoginEmailValues(); - // 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("/hint"); + await this.router.navigateByUrl("/hint"); } - goToRegister() { + async goToRegister() { const email = this.formGroup.value.email; if (email) { - // 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(["/register"], { queryParams: { email: email } }); + await this.router.navigate(["/register"], { queryParams: { email: email } }); 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(["/register"]); + await this.router.navigate(["/register"]); } - protected override handleMigrateEncryptionKey(result: AuthResult): boolean { + protected override async handleMigrateEncryptionKey(result: AuthResult): Promise { if (!result.requiresEncryptionKeyMigration) { 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(["migrate-legacy-encryption"]); + await this.router.navigate(["migrate-legacy-encryption"]); return true; } + + private async initPasswordPolicies(invite: OrganizationInvite): Promise { + try { + this.policies = await this.policyApiService.getPoliciesByToken( + invite.organizationId, + invite.token, + invite.email, + invite.organizationUserId, + ); + } catch (e) { + this.logService.error(e); + } + + if (this.policies == null) { + return; + } + const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( + this.policies, + invite.organizationId, + ); + // Set to true if policy enabled and auto-enroll enabled + this.showResetPasswordAutoEnrollWarning = + resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; + + this.policyService + .masterPasswordPolicyOptions$(this.policies) + .pipe(takeUntil(this.destroy$)) + .subscribe((enforcedPasswordPolicyOptions) => { + this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions; + }); + } } diff --git a/apps/web/src/app/auth/accept-organization.component.html b/apps/web/src/app/auth/organization-invite/accept-organization.component.html similarity index 97% rename from apps/web/src/app/auth/accept-organization.component.html rename to apps/web/src/app/auth/organization-invite/accept-organization.component.html index 3aef47df22b..f9dd3da5ed9 100644 --- a/apps/web/src/app/auth/accept-organization.component.html +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.html @@ -18,7 +18,7 @@

- {{ orgName }} + {{ 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 new file mode 100644 index 00000000000..fa5507b216f --- /dev/null +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -0,0 +1,94 @@ +import { Component } from "@angular/core"; +import { ActivatedRoute, Params, Router } from "@angular/router"; + +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { BaseAcceptComponent } from "../../common/base.accept.component"; + +import { AcceptOrganizationInviteService } from "./accept-organization.service"; +import { OrganizationInvite } from "./organization-invite"; + +@Component({ + templateUrl: "accept-organization.component.html", +}) +export class AcceptOrganizationComponent extends BaseAcceptComponent { + orgName$ = this.acceptOrganizationInviteService.orgName$; + protected requiredParameters: string[] = ["organizationId", "organizationUserId", "token"]; + + constructor( + router: Router, + platformUtilsService: PlatformUtilsService, + i18nService: I18nService, + route: ActivatedRoute, + authService: AuthService, + private acceptOrganizationInviteService: AcceptOrganizationInviteService, + ) { + super(router, platformUtilsService, i18nService, route, authService); + } + + async authedHandler(qParams: Params): Promise { + const invite = OrganizationInvite.fromParams(qParams); + const success = await this.acceptOrganizationInviteService.validateAndAcceptInvite(invite); + + if (!success) { + return; + } + + this.platformUtilService.showToast( + "success", + this.i18nService.t("inviteAccepted"), + invite.initOrganization + ? this.i18nService.t("inviteInitAcceptedDesc") + : this.i18nService.t("inviteAcceptedDesc"), + { timeout: 10000 }, + ); + + await this.router.navigate(["/vault"]); + } + + async unauthedHandler(qParams: Params): Promise { + const invite = OrganizationInvite.fromParams(qParams); + await this.acceptOrganizationInviteService.setOrganizationInvitation(invite); + await this.accelerateInviteAcceptIfPossible(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; + } + + // if user exists, send user to login + if (invite.orgUserHasExistingUser) { + await this.router.navigate(["/login"], { + queryParams: { email: invite.email }, + }); + return; + } + + if (invite.orgSsoIdentifier) { + // We only send sso org identifier if the org has SSO enabled and the SSO policy required. + // Will JIT provision the user. + // Note: If the organization has Admin Recovery enabled, the user will be accepted into the org + // upon enrollment. The user should not be returned here. + await this.router.navigate(["/sso"], { + queryParams: { email: invite.email, identifier: invite.orgSsoIdentifier }, + }); + return; + } + + // if SSO is disabled OR if sso is enabled but the SSO login required policy is not enabled + // then send user to create account + await this.router.navigate(["/register"], { + queryParams: { email: invite.email, fromOrgInvite: true }, + }); + return; + } +} diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.module.ts b/apps/web/src/app/auth/organization-invite/accept-organization.module.ts new file mode 100644 index 00000000000..3dc0e144891 --- /dev/null +++ b/apps/web/src/app/auth/organization-invite/accept-organization.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from "@angular/core"; + +import { SharedModule } from "../../shared"; + +import { AcceptOrganizationComponent } from "./accept-organization.component"; +import { AcceptOrganizationInviteService } from "./accept-organization.service"; + +@NgModule({ + declarations: [AcceptOrganizationComponent], + imports: [SharedModule], + providers: [AcceptOrganizationInviteService], +}) +export class AcceptOrganizationInviteModule {} 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 new file mode 100644 index 00000000000..97a17a5997f --- /dev/null +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -0,0 +1,185 @@ +import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; +import { MockProxy, mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +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 { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { FakeGlobalState } from "@bitwarden/common/spec/fake-state"; +import { OrgKey } from "@bitwarden/common/types/key"; + +import { I18nService } from "../../core/i18n.service"; + +import { + AcceptOrganizationInviteService, + ORGANIZATION_INVITE, +} from "./accept-organization.service"; +import { OrganizationInvite } from "./organization-invite"; + +describe("AcceptOrganizationInviteService", () => { + let sut: AcceptOrganizationInviteService; + let apiService: MockProxy; + let authService: MockProxy; + let cryptoService: MockProxy; + let encryptService: MockProxy; + let policyApiService: MockProxy; + let policyService: MockProxy; + let logService: MockProxy; + let organizationApiService: MockProxy; + let organizationUserService: MockProxy; + let i18nService: MockProxy; + let globalStateProvider: FakeGlobalStateProvider; + let globalState: FakeGlobalState; + + beforeEach(() => { + apiService = mock(); + authService = mock(); + cryptoService = mock(); + encryptService = mock(); + policyApiService = mock(); + policyService = mock(); + logService = mock(); + organizationApiService = mock(); + organizationUserService = mock(); + i18nService = mock(); + globalStateProvider = new FakeGlobalStateProvider(); + globalState = globalStateProvider.getFake(ORGANIZATION_INVITE); + + sut = new AcceptOrganizationInviteService( + apiService, + authService, + cryptoService, + encryptService, + policyApiService, + policyService, + logService, + organizationApiService, + organizationUserService, + i18nService, + globalStateProvider, + ); + }); + + describe("validateAndAcceptInvite", () => { + it("initializes an organization when given an invite where initOrganization is true", async () => { + cryptoService.makeOrgKey.mockResolvedValue([ + { encryptedString: "string" } as EncString, + "orgPrivateKey" as unknown as OrgKey, + ]); + cryptoService.makeKeyPair.mockResolvedValue([ + "orgPublicKey", + { encryptedString: "string" } as EncString, + ]); + encryptService.encrypt.mockResolvedValue({ encryptedString: "string" } as EncString); + const invite = createOrgInvite({ initOrganization: true }); + + const result = await sut.validateAndAcceptInvite(invite); + + expect(result).toBe(true); + expect(organizationUserService.postOrganizationUserAcceptInit).toHaveBeenCalled(); + expect(apiService.refreshIdentityToken).toHaveBeenCalled(); + expect(globalState.nextMock).toHaveBeenCalledWith(null); + expect(organizationUserService.postOrganizationUserAccept).not.toHaveBeenCalled(); + expect(authService.logOut).not.toHaveBeenCalled(); + }); + + it("logs out the user and stores the invite when a master password policy check is required", async () => { + const invite = createOrgInvite(); + policyApiService.getPoliciesByToken.mockResolvedValue([ + { + type: PolicyType.MasterPassword, + enabled: true, + } as Policy, + ]); + + const result = await sut.validateAndAcceptInvite(invite); + + expect(result).toBe(false); + expect(authService.logOut).toHaveBeenCalled(); + expect(globalState.nextMock).toHaveBeenCalledWith(invite); + }); + + it("clears the stored invite when a master password policy check is required but the stored invite doesn't match the provided one", async () => { + const storedInvite = createOrgInvite({ email: "wrongemail@example.com" }); + const providedInvite = createOrgInvite(); + await globalState.update(() => storedInvite); + policyApiService.getPoliciesByToken.mockResolvedValue([ + { + type: PolicyType.MasterPassword, + enabled: true, + } as Policy, + ]); + + const result = await sut.validateAndAcceptInvite(providedInvite); + + expect(result).toBe(false); + expect(authService.logOut).toHaveBeenCalled(); + expect(globalState.nextMock).toHaveBeenCalledWith(providedInvite); + }); + + it("accepts the invitation request when the organization doesn't have a master password policy", async () => { + const invite = createOrgInvite(); + policyApiService.getPoliciesByToken.mockResolvedValue([]); + + const result = await sut.validateAndAcceptInvite(invite); + + expect(result).toBe(true); + expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled(); + expect(apiService.refreshIdentityToken).toHaveBeenCalled(); + expect(globalState.nextMock).toHaveBeenCalledWith(null); + expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); + expect(authService.logOut).not.toHaveBeenCalled(); + }); + + it("accepts the invitation request when the org has a master password policy, but the user has already passed it", async () => { + const invite = createOrgInvite(); + policyApiService.getPoliciesByToken.mockResolvedValue([ + { + type: PolicyType.MasterPassword, + enabled: true, + } as Policy, + ]); + // an existing invite means the user has already passed the master password policy + await globalState.update(() => invite); + + policyService.getResetPasswordPolicyOptions.mockReturnValue([ + { + autoEnrollEnabled: false, + } as ResetPasswordPolicyOptions, + false, + ]); + + const result = await sut.validateAndAcceptInvite(invite); + + expect(result).toBe(true); + expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled(); + expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); + expect(authService.logOut).not.toHaveBeenCalled(); + }); + }); +}); + +function createOrgInvite(custom: Partial = {}): OrganizationInvite { + return Object.assign( + { + email: "user@example.com", + initOrganization: false, + orgSsoIdentifier: null, + orgUserHasExistingUser: false, + organizationId: "organizationId", + organizationName: "organizationName", + organizationUserId: "organizationUserId", + token: "token", + }, + custom, + ); +} 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 new file mode 100644 index 00000000000..e43023c37d7 --- /dev/null +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -0,0 +1,248 @@ +import { Injectable } from "@angular/core"; +import { BehaviorSubject, firstValueFrom, map } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +import { + OrganizationUserAcceptRequest, + OrganizationUserAcceptInitRequest, +} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; +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 { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { + GlobalState, + GlobalStateProvider, + KeyDefinition, + ORGANIZATION_INVITE_DISK, +} from "@bitwarden/common/platform/state"; +import { OrgKey } from "@bitwarden/common/types/key"; + +import { OrganizationInvite } from "./organization-invite"; + +// We're storing the organization invite for 2 reasons: +// 1. If the org requires a MP policy check, we need to keep track that the user has already been redirected when they return. +// 2. The MP policy check happens on login/register flows, we need to store the token to retrieve the policies then. +export const ORGANIZATION_INVITE = new KeyDefinition( + ORGANIZATION_INVITE_DISK, + "organizationInvite", + { + deserializer: (invite) => OrganizationInvite.fromJSON(invite), + }, +); + +@Injectable() +export class AcceptOrganizationInviteService { + private organizationInvitationState: GlobalState; + private orgNameSubject: BehaviorSubject = new BehaviorSubject(null); + private policyCache: Policy[]; + + // Fix URL encoding of space issue with Angular + orgName$ = this.orgNameSubject.pipe(map((orgName) => orgName.replace(/\+/g, " "))); + + constructor( + private readonly apiService: ApiService, + private readonly authService: AuthService, + private readonly cryptoService: CryptoService, + private readonly encryptService: EncryptService, + private readonly policyApiService: PolicyApiServiceAbstraction, + private readonly policyService: PolicyService, + private readonly logService: LogService, + private readonly organizationApiService: OrganizationApiServiceAbstraction, + private readonly organizationUserService: OrganizationUserService, + private readonly i18nService: I18nService, + private readonly globalStateProvider: GlobalStateProvider, + ) { + this.organizationInvitationState = this.globalStateProvider.get(ORGANIZATION_INVITE); + } + + /** Returns the currently stored organization invite */ + async getOrganizationInvite(): Promise { + return await firstValueFrom(this.organizationInvitationState.state$); + } + + /** + * Stores a new organization invite + * @param invite an organization invite + * @throws if the invite is nullish + */ + async setOrganizationInvitation(invite: OrganizationInvite): Promise { + if (invite == null) { + throw new Error("Invite cannot be null. Use clearOrganizationInvitation instead."); + } + await this.organizationInvitationState.update(() => invite); + } + + /** Clears the currently stored organization invite */ + async clearOrganizationInvitation(): Promise { + await this.organizationInvitationState.update(() => null); + } + + /** + * Validates and accepts the organization invitation if possible. + * Note: Users might need to pass a MP policy check before accepting an invite to an existing organization. If the user + * has not passed this check, they will be logged out and the invite will be stored for later use. + * @param invite an organization invite + * @returns a promise that resolves a boolean indicating if the invite was accepted. + */ + async validateAndAcceptInvite(invite: OrganizationInvite): Promise { + if (invite == null) { + throw new Error("Invite cannot be null."); + } + + // Creation of a new org + if (invite.initOrganization) { + await this.acceptAndInitOrganization(invite); + return true; + } + + // Accepting an org invite from existing org + if (await this.masterPasswordPolicyCheckRequired(invite)) { + await this.setOrganizationInvitation(invite); + this.authService.logOut(() => { + /* Do nothing */ + }); + return false; + } + + // We know the user has already logged in and passed a MP policy check + await this.accept(invite); + return true; + } + + private async acceptAndInitOrganization(invite: OrganizationInvite): Promise { + await this.prepareAcceptAndInitRequest(invite).then((request) => + this.organizationUserService.postOrganizationUserAcceptInit( + invite.organizationId, + invite.organizationUserId, + request, + ), + ); + await this.apiService.refreshIdentityToken(); + await this.clearOrganizationInvitation(); + } + + private async prepareAcceptAndInitRequest( + invite: OrganizationInvite, + ): Promise { + const request = new OrganizationUserAcceptInitRequest(); + request.token = invite.token; + + const [encryptedOrgKey, orgKey] = await this.cryptoService.makeOrgKey(); + const [orgPublicKey, encryptedOrgPrivateKey] = await this.cryptoService.makeKeyPair(orgKey); + const collection = await this.encryptService.encrypt( + this.i18nService.t("defaultCollection"), + orgKey, + ); + + request.key = encryptedOrgKey.encryptedString; + request.keys = new OrganizationKeysRequest( + orgPublicKey, + encryptedOrgPrivateKey.encryptedString, + ); + request.collectionName = collection.encryptedString; + + return request; + } + + private async accept(invite: OrganizationInvite): Promise { + await this.prepareAcceptRequest(invite).then((request) => + this.organizationUserService.postOrganizationUserAccept( + invite.organizationId, + invite.organizationUserId, + request, + ), + ); + + await this.apiService.refreshIdentityToken(); + await this.clearOrganizationInvitation(); + } + + private async prepareAcceptRequest( + invite: OrganizationInvite, + ): Promise { + const request = new OrganizationUserAcceptRequest(); + request.token = invite.token; + + if (await this.resetPasswordEnrollRequired(invite)) { + const response = await this.organizationApiService.getKeys(invite.organizationId); + + if (response == null) { + throw new Error(this.i18nService.t("resetPasswordOrgKeysError")); + } + + const publicKey = Utils.fromB64ToArray(response.publicKey); + + // RSA Encrypt user's encKey.key with organization public key + const userKey = await this.cryptoService.getUserKey(); + const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey); + + // Add reset password key to accept request + request.resetPasswordKey = encryptedKey.encryptedString; + } + return request; + } + + private async resetPasswordEnrollRequired(invite: OrganizationInvite): Promise { + const policies = await this.getPolicies(invite); + + if (policies == null || policies.length === 0) { + return false; + } + + const result = this.policyService.getResetPasswordPolicyOptions( + policies, + invite.organizationId, + ); + // Return true if policy enabled and auto-enroll enabled + return result[1] && result[0].autoEnrollEnabled; + } + + private async masterPasswordPolicyCheckRequired(invite: OrganizationInvite): Promise { + const policies = await this.getPolicies(invite); + + if (policies == null || policies.length === 0) { + return false; + } + const hasMasterPasswordPolicy = policies.some( + (p) => p.type === PolicyType.MasterPassword && p.enabled, + ); + + let storedInvite = await this.getOrganizationInvite(); + if (storedInvite?.email !== invite.email) { + // clear stored invites if the email doesn't match + await this.clearOrganizationInvitation(); + storedInvite = null; + } + // if we don't have an org invite stored, we know the user hasn't been redirected yet to check the MP policy + const hasNotCheckedMasterPasswordYet = storedInvite == null; + return hasMasterPasswordPolicy && hasNotCheckedMasterPasswordYet; + } + + private async getPolicies(invite: OrganizationInvite): Promise { + // if policies are not cached, fetch them + if (this.policyCache == null) { + try { + this.policyCache = await this.policyApiService.getPoliciesByToken( + invite.organizationId, + invite.token, + invite.email, + invite.organizationUserId, + ); + } catch (e) { + this.logService.error(e); + } + } + + return this.policyCache; + } +} diff --git a/apps/web/src/app/auth/organization-invite/organization-invite.ts b/apps/web/src/app/auth/organization-invite/organization-invite.ts new file mode 100644 index 00000000000..9a0bbf83348 --- /dev/null +++ b/apps/web/src/app/auth/organization-invite/organization-invite.ts @@ -0,0 +1,30 @@ +import { Params } from "@angular/router"; +import { Jsonify } from "type-fest"; + +export class OrganizationInvite { + email: string; + initOrganization: boolean; + orgSsoIdentifier: string; + orgUserHasExistingUser: boolean; + organizationId: string; + organizationName: string; + organizationUserId: string; + token: string; + + static fromJSON(json: Jsonify) { + return Object.assign(new OrganizationInvite(), json); + } + + static fromParams(params: Params): OrganizationInvite { + return Object.assign(new OrganizationInvite(), { + email: params.email, + initOrganization: params.initOrganization?.toLocaleLowerCase() === "true", + orgSsoIdentifier: params.orgSsoIdentifier, + orgUserHasExistingUser: params.orgUserHasExistingUser?.toLocaleLowerCase() === "true", + organizationId: params.organizationId, + organizationName: params.organizationName, + organizationUserId: params.organizationUserId, + token: params.token, + }); + } +} diff --git a/apps/web/src/app/auth/recover-two-factor.component.html b/apps/web/src/app/auth/recover-two-factor.component.html index 11d281b742b..e3641765800 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.html +++ b/apps/web/src/app/auth/recover-two-factor.component.html @@ -1,76 +1,40 @@ - -
-
-

{{ "recoverAccountTwoStep" | i18n }}

-
-
-

- {{ "recoverAccountTwoStepDesc" | i18n }} - {{ "learnMore" | i18n }} -

-
- - -
-
- - -
-
- - -
-
-
- - - {{ "cancel" | i18n }} - -
-
-
-
+ +

+ {{ "recoverAccountTwoStepDesc" | i18n }} + {{ "learnMore" | i18n }} +

+ + {{ "emailAddress" | i18n }} + + + + {{ "masterPass" | i18n }} + + + + {{ "recoveryCodeTitle" | i18n }} + + +
+
+ + + {{ "cancel" | i18n }} +
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 145c46c8df5..4996dbe0a50 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -1,4 +1,5 @@ import { Component } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; @@ -6,7 +7,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorRecoveryRequest } from "@bitwarden/common/auth/models/request/two-factor-recovery.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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"; @Component({ @@ -14,10 +14,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl templateUrl: "recover-two-factor.component.html", }) export class RecoverTwoFactorComponent { - email: string; - masterPassword: string; - recoveryCode: string; - formPromise: Promise; + protected formGroup = new FormGroup({ + email: new FormControl(null, [Validators.required]), + masterPassword: new FormControl(null, [Validators.required]), + recoveryCode: new FormControl(null, [Validators.required]), + }); constructor( private router: Router, @@ -26,31 +27,32 @@ export class RecoverTwoFactorComponent { private i18nService: I18nService, private cryptoService: CryptoService, private loginStrategyService: LoginStrategyServiceAbstraction, - private logService: LogService, ) {} - async submit() { - try { - const request = new TwoFactorRecoveryRequest(); - request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); - request.email = this.email.trim().toLowerCase(); - const key = await this.loginStrategyService.makePreloginKey( - this.masterPassword, - request.email, - ); - request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); - this.formPromise = this.apiService.postTwoFactorRecover(request); - await this.formPromise; - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("twoStepRecoverDisabled"), - ); - // 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(["/"]); - } catch (e) { - this.logService.error(e); - } + get email(): string { + return this.formGroup.value.email; } + + get masterPassword(): string { + return this.formGroup.value.masterPassword; + } + + get recoveryCode(): string { + return this.formGroup.value.recoveryCode; + } + + submit = async () => { + const request = new TwoFactorRecoveryRequest(); + request.recoveryCode = this.recoveryCode.replace(/\s/g, "").toLowerCase(); + request.email = this.email.trim().toLowerCase(); + const key = await this.loginStrategyService.makePreloginKey(this.masterPassword, request.email); + request.masterPasswordHash = await this.cryptoService.hashMasterKey(this.masterPassword, key); + await this.apiService.postTwoFactorRecover(request); + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("twoStepRecoverDisabled"), + ); + await this.router.navigate(["/"]); + }; } 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 6c1b3122c6e..4532cf14050 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 @@ -10,6 +10,7 @@ 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 { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; +import { RegisterRequest } from "@bitwarden/common/models/request/register.request"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -19,6 +20,8 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { DialogService } from "@bitwarden/components"; +import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; + @Component({ selector: "app-register-form", templateUrl: "./register-form.component.html", @@ -48,6 +51,7 @@ export class RegisterFormComponent extends BaseRegisterComponent { logService: LogService, auditService: AuditService, dialogService: DialogService, + acceptOrgInviteService: AcceptOrganizationInviteService, ) { super( formValidationErrorService, @@ -65,6 +69,16 @@ export class RegisterFormComponent extends BaseRegisterComponent { auditService, dialogService, ); + super.modifyRegisterRequest = async (request: RegisterRequest) => { + // Org invites are deep linked. Non-existent accounts are redirected to the register page. + // Org user id and token are included here only for validation and two factor purposes. + const orgInvite = await acceptOrgInviteService.getOrganizationInvite(); + if (orgInvite != null) { + request.organizationUserId = orgInvite.organizationUserId; + request.token = orgInvite.token; + } + // Invite is accepted after login (on deep link redirect). + }; } async ngOnInit() { diff --git a/apps/web/src/app/auth/remove-password.component.html b/apps/web/src/app/auth/remove-password.component.html index 19e0d63572b..f8d2b26fc17 100644 --- a/apps/web/src/app/auth/remove-password.component.html +++ b/apps/web/src/app/auth/remove-password.component.html @@ -1,55 +1,33 @@ -
-
- -

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

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

{{ "removeMasterPassword" | i18n }}

-
-
-
-

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

- - -
-
-
-
+
+

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

+ + +
diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index accde2e9a09..ccd329dd640 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -1,9 +1,30 @@ -import { Component } from "@angular/core"; +import { Component, inject } from "@angular/core"; import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { MasterKey, UserKey } from "@bitwarden/common/types/key"; + +import { RouterService } from "../core"; + +import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service"; @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", }) -export class SetPasswordComponent extends BaseSetPasswordComponent {} +export class SetPasswordComponent extends BaseSetPasswordComponent { + routerService = inject(RouterService); + acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); + + protected override async onSetPasswordSuccess( + masterKey: MasterKey, + userKey: [UserKey, EncString], + keyPair: [string, EncString], + ): Promise { + await super.onSetPasswordSuccess(masterKey, userKey, keyPair); + // SSO JIT accepts org invites when setting their MP, meaning + // we can clear the deep linked url for accepting it. + await this.routerService.getAndClearLoginRedirectUrl(); + await this.acceptOrganizationInviteService.clearOrganizationInvitation(); + } +} diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html new file mode 100644 index 00000000000..fd65192beea --- /dev/null +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.html @@ -0,0 +1,61 @@ + + +
+ + {{ "loading" | i18n }} +
+ + {{ error }} + +

{{ "pickAnAvatarColor" | i18n }}

+
+ + + + + + + + + + +
+
+ + + + +
diff --git a/apps/web/src/app/auth/settings/account/change-avatar.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts similarity index 78% rename from apps/web/src/app/auth/settings/account/change-avatar.component.ts rename to apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index bbcbaf6820f..6946f8b94bb 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -1,11 +1,10 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, ElementRef, - EventEmitter, - Input, + Inject, OnDestroy, OnInit, - Output, ViewChild, ViewEncapsulation, } from "@angular/core"; @@ -14,20 +13,20 @@ import { BehaviorSubject, debounceTime, firstValueFrom, Subject, takeUntil } fro import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { DialogService } from "@bitwarden/components"; + +type ChangeAvatarDialogData = { + profile: ProfileResponse; +}; @Component({ - selector: "app-change-avatar", - templateUrl: "change-avatar.component.html", + templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, }) -export class ChangeAvatarComponent implements OnInit, OnDestroy { - @Input() profile: ProfileResponse; - - @Output() changeColor: EventEmitter = new EventEmitter(); - @Output() onSaved = new EventEmitter(); +export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { + profile: ProfileResponse; @ViewChild("colorPicker") colorPickerElement: ElementRef; @@ -52,11 +51,14 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); constructor( + @Inject(DIALOG_DATA) protected data: ChangeAvatarDialogData, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private logService: LogService, private avatarService: AvatarService, - ) {} + private dialogRef: DialogRef, + ) { + this.profile = data.profile; + } async ngOnInit() { //localize the default colors @@ -88,20 +90,15 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { Utils.stringToColor(this.profile.name.toString()); } - async submit() { - try { - if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) { - await this.avatarService.setAvatarColor(this.currentSelection); - this.changeColor.emit(this.currentSelection); - this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated")); - } else { - this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); - } - } catch (e) { - this.logService.error(e); + submit = async () => { + if (Utils.validateHexColor(this.currentSelection) || this.currentSelection == null) { + await this.avatarService.setAvatarColor(this.currentSelection); + this.dialogRef.close(); + this.platformUtilsService.showToast("success", null, this.i18nService.t("avatarUpdated")); + } else { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } - } + }; async ngOnDestroy() { this.destroy$.next(); @@ -131,6 +128,10 @@ export class ChangeAvatarComponent implements OnInit, OnDestroy { } } } + + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(ChangeAvatarDialogComponent, config); + } } export class NamedAvatarColor { diff --git a/apps/web/src/app/auth/settings/account/change-avatar.component.html b/apps/web/src/app/auth/settings/account/change-avatar.component.html deleted file mode 100644 index 3a974241d5f..00000000000 --- a/apps/web/src/app/auth/settings/account/change-avatar.component.html +++ /dev/null @@ -1,84 +0,0 @@ - - - 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 db6ab2b6588..4464824c63e 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.html +++ b/apps/web/src/app/auth/settings/account/profile.component.html @@ -23,6 +23,7 @@ - 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 64c5687c0b8..33463b689c9 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -1,28 +1,25 @@ -import { ViewChild, ViewContainerRef, Component, OnDestroy, OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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"; -import { ChangeAvatarComponent } from "./change-avatar.component"; +import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", }) -export class ProfileComponent implements OnInit, OnDestroy { +export class ProfileComponent implements OnInit { loading = true; profile: ProfileResponse; fingerprintMaterial: string; - - @ViewChild("avatarModalTemplate", { read: ViewContainerRef, static: true }) - avatarModalRef: ViewContainerRef; private destroy$ = new Subject(); protected formGroup = new FormGroup({ @@ -35,7 +32,7 @@ export class ProfileComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private stateService: StateService, - private modalService: ModalService, + private dialogService: DialogService, ) {} async ngOnInit() { @@ -53,24 +50,17 @@ export class ProfileComponent implements OnInit, OnDestroy { }); } + openChangeAvatar = async () => { + ChangeAvatarDialogComponent.open(this.dialogService, { + data: { profile: this.profile }, + }); + }; + async ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } - openChangeAvatar = async () => { - const modalOpened = await this.modalService.openViewRef( - ChangeAvatarComponent, - this.avatarModalRef, - (modal) => { - modal.profile = this.profile; - modal.changeColor.pipe(takeUntil(this.destroy$)).subscribe(() => { - modalOpened[0].close(); - }); - }, - ); - }; - submit = async () => { const request = new UpdateProfileRequest( this.formGroup.get("name").value, diff --git a/apps/web/src/app/auth/settings/security/api-key.component.html b/apps/web/src/app/auth/settings/security/api-key.component.html index 1402a993881..118b17643cc 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.html +++ b/apps/web/src/app/auth/settings/security/api-key.component.html @@ -1,72 +1,42 @@ - +
+ + {{ data.apiKeyTitle | i18n }} +
+

{{ data.apiKeyDescription | i18n }}

+ + + {{ data.apiKeyWarning | i18n }} + +

+ client_id:
+ {{ clientId }} +

+

+ client_secret:
+ {{ clientSecret }} +

+

+ scope:
+ {{ data.scope }} +

+

+ grant_type:
+ {{ data.grantType }} +

+
+
+
+ + +
+
+
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 9d005562725..d171bc35617 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,46 +1,58 @@ -import { Component } from "@angular/core"; +import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { ApiKeyResponse } from "@bitwarden/common/auth/models/response/api-key.response"; import { Verification } from "@bitwarden/common/auth/types/verification"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { DialogService } from "@bitwarden/components"; -@Component({ - selector: "app-api-key", - templateUrl: "api-key.component.html", -}) -export class ApiKeyComponent { +export type ApiKeyDialogData = { keyType: string; - isRotation: boolean; - postKey: (entityId: string, request: SecretVerificationRequest) => Promise; + isRotation?: boolean; entityId: string; + postKey: (entityId: string, request: SecretVerificationRequest) => Promise; scope: string; grantType: string; apiKeyTitle: string; apiKeyWarning: string; apiKeyDescription: string; - - masterPassword: Verification; - formPromise: Promise; +}; +@Component({ + selector: "app-api-key", + templateUrl: "api-key.component.html", +}) +export class ApiKeyComponent { clientId: string; clientSecret: string; + formGroup = this.formBuilder.group({ + masterPassword: [null as Verification, [Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: ApiKeyDialogData, + private formBuilder: FormBuilder, private userVerificationService: UserVerificationService, - private logService: LogService, ) {} - async submit() { - try { - this.formPromise = this.userVerificationService - .buildRequest(this.masterPassword) - .then((request) => this.postKey(this.entityId, request)); - const response = await this.formPromise; - this.clientSecret = response.apiKey; - this.clientId = `${this.keyType}.${this.entityId}`; - } catch (e) { - this.logService.error(e); + submit = async () => { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; } - } + const response = await this.userVerificationService + .buildRequest(this.formGroup.value.masterPassword) + .then((request) => this.data.postKey(this.data.entityId, request)); + this.clientSecret = response.apiKey; + this.clientId = `${this.data.keyType}.${this.data.entityId}`; + }; + /** + * Strongly typed helper to open a ApiKeyComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open = (dialogService: DialogService, config: DialogConfig) => { + return dialogService.open(ApiKeyComponent, config); + }; } 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 e29417fad74..8de629dc83e 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,9 +1,9 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { DialogService } from "@bitwarden/components"; import { ApiKeyComponent } from "./api-key.component"; @@ -22,8 +22,8 @@ export class SecurityKeysComponent implements OnInit { constructor( private userVerificationService: UserVerificationService, private stateService: StateService, - private modalService: ModalService, private apiService: ApiService, + private dialogService: DialogService, ) {} async ngOnInit() { @@ -32,30 +32,34 @@ export class SecurityKeysComponent implements OnInit { async viewUserApiKey() { const entityId = await this.stateService.getUserId(); - await this.modalService.openViewRef(ApiKeyComponent, this.viewUserApiKeyModalRef, (comp) => { - comp.keyType = "user"; - comp.entityId = entityId; - comp.postKey = this.apiService.postUserApiKey.bind(this.apiService); - comp.scope = "api"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "userApiKeyWarning"; - comp.apiKeyDescription = "userApiKeyDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "user", + entityId: entityId, + postKey: this.apiService.postUserApiKey.bind(this.apiService), + scope: "api", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "userApiKeyWarning", + apiKeyDescription: "userApiKeyDesc", + }, }); } async rotateUserApiKey() { const entityId = await this.stateService.getUserId(); - await this.modalService.openViewRef(ApiKeyComponent, this.rotateUserApiKeyModalRef, (comp) => { - comp.keyType = "user"; - comp.isRotation = true; - comp.entityId = entityId; - comp.postKey = this.apiService.postUserRotateApiKey.bind(this.apiService); - comp.scope = "api"; - comp.grantType = "client_credentials"; - comp.apiKeyTitle = "apiKey"; - comp.apiKeyWarning = "userApiKeyWarning"; - comp.apiKeyDescription = "apiKeyRotateDesc"; + await ApiKeyComponent.open(this.dialogService, { + data: { + keyType: "user", + isRotation: true, + entityId: entityId, + postKey: this.apiService.postUserRotateApiKey.bind(this.apiService), + scope: "api", + grantType: "client_credentials", + apiKeyTitle: "apiKey", + apiKeyWarning: "userApiKeyWarning", + apiKeyDescription: "apiKeyRotateDesc", + }, }); } } diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html index e17714cca79..a7efaed731e 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.html @@ -1,109 +1,80 @@ - +
+ + {{ "twoStepLogin" | i18n }} + {{ "authenticatorAppTitle" | i18n }} + + + + Authenticator app logo +

{{ "twoStepAuthenticatorDesc" | i18n }}

+

+ 1. {{ "twoStepAuthenticatorDownloadApp" | i18n }} +

+
+ + +

{{ "twoStepLoginProviderEnabled" | i18n }}

+ {{ "twoStepAuthenticatorReaddDesc" | i18n }} +
+ Authenticator app logo +

{{ "twoStepAuthenticatorNeedApp" | i18n }}

+
+ +

{{ "twoStepAuthenticatorAppsRecommended" | i18n }}

+

+ 2. {{ "twoStepAuthenticatorScanCode" | i18n }} +

+
+

+
+ {{ key }} +

+ + + 3. {{ "twoStepAuthenticatorEnterCode" | i18n }} + + + +
+ + + + +
+
diff --git a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts index 88b695eb728..17cdbb595f7 100644 --- a/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-authenticator.component.ts @@ -1,4 +1,6 @@ -import { Component, OnDestroy, OnInit } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -38,15 +40,21 @@ export class TwoFactorAuthenticatorComponent extends TwoFactorBaseComponent implements OnInit, OnDestroy { + @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.Authenticator; key: string; - token: string; formPromise: Promise; override componentName = "app-two-factor-authenticator"; private qrScript: HTMLScriptElement; + protected formGroup = new FormGroup({ + token: new FormControl(null, [Validators.required]), + }); + constructor( + @Inject(DIALOG_DATA) protected data: AuthResponse, + private dialogRef: DialogRef, apiService: ApiService, i18nService: I18nService, userVerificationService: UserVerificationService, @@ -68,8 +76,9 @@ export class TwoFactorAuthenticatorComponent this.qrScript.async = true; } - ngOnInit() { + async ngOnInit() { window.document.body.appendChild(this.qrScript); + await this.auth(this.data); } ngOnDestroy() { @@ -81,17 +90,24 @@ export class TwoFactorAuthenticatorComponent return this.processResponse(authResponse.response); } - submit() { + submit = async () => { if (this.enabled) { - return super.disable(this.formPromise); + await this.disableAuthentication(this.formPromise); + this.onChangeStatus.emit(this.enabled); + this.close(); } else { - return this.enable(); + await this.enable(); + this.onChangeStatus.emit(this.enabled); } + }; + + private async disableAuthentication(promise: Promise) { + return super.disable(promise); } protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest); - request.token = this.token; + request.token = this.formGroup.value.token; request.key = this.key; return super.enable(async () => { @@ -102,7 +118,7 @@ export class TwoFactorAuthenticatorComponent } private async processResponse(response: TwoFactorAuthenticatorResponse) { - this.token = null; + this.formGroup.get("token").setValue(null); this.enabled = response.enabled; this.key = response.key; const email = await firstValueFrom( @@ -121,4 +137,15 @@ export class TwoFactorAuthenticatorComponent }); }, 100); } + + close = () => { + this.dialogRef.close(this.enabled); + }; + + static open( + dialogService: DialogService, + config: DialogConfig>, + ) { + return dialogService.open(TwoFactorAuthenticatorComponent, config); + } } diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.html b/apps/web/src/app/auth/settings/two-factor-email.component.html index 93a6b0bb18a..cf1dba98842 100644 --- a/apps/web/src/app/auth/settings/two-factor-email.component.html +++ b/apps/web/src/app/auth/settings/two-factor-email.component.html @@ -1,101 +1,53 @@ - + + 2. {{ "twoFactorEmailEnterCode" | i18n }} + + + + + + + + + + diff --git a/apps/web/src/app/auth/settings/two-factor-email.component.ts b/apps/web/src/app/auth/settings/two-factor-email.component.ts index 7a2e6de5801..8a5c0292230 100644 --- a/apps/web/src/app/auth/settings/two-factor-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-email.component.ts @@ -1,4 +1,6 @@ -import { Component } from "@angular/core"; +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, Output } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -19,18 +21,22 @@ import { TwoFactorBaseComponent } from "./two-factor-base.component"; @Component({ selector: "app-two-factor-email", templateUrl: "two-factor-email.component.html", + outputs: ["onUpdated"], }) export class TwoFactorEmailComponent extends TwoFactorBaseComponent { + @Output() onChangeStatus: EventEmitter = new EventEmitter(); type = TwoFactorProviderType.Email; - email: string; - token: string; sentEmail: string; formPromise: Promise; emailPromise: Promise; - override componentName = "app-two-factor-email"; + formGroup = this.formBuilder.group({ + token: [null], + email: ["", [Validators.email, Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: AuthResponse, apiService: ApiService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, @@ -38,6 +44,8 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { userVerificationService: UserVerificationService, private accountService: AccountService, dialogService: DialogService, + private formBuilder: FormBuilder, + private dialogRef: DialogRef, ) { super( apiService, @@ -48,31 +56,49 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { dialogService, ); } + get token() { + return this.formGroup.get("token").value; + } + set token(value: string) { + this.formGroup.get("token").setValue(value); + } + get email() { + return this.formGroup.get("email").value; + } + set email(value: string) { + this.formGroup.get("email").setValue(value); + } + + async ngOnInit() { + await this.auth(this.data); + } auth(authResponse: AuthResponse) { super.auth(authResponse); return this.processResponse(authResponse.response); } - submit() { + submit = async () => { if (this.enabled) { - return super.disable(this.formPromise); + await this.disableEmail(); + this.onChangeStatus.emit(false); } else { - return this.enable(); + await this.enable(); + this.onChangeStatus.emit(true); } + }; + + private disableEmail() { + return super.disable(this.formPromise); } - async sendEmail() { - try { - const request = await this.buildRequestModel(TwoFactorEmailRequest); - request.email = this.email; - this.emailPromise = this.apiService.postTwoFactorEmailSetup(request); - await this.emailPromise; - this.sentEmail = this.email; - } catch (e) { - this.logService.error(e); - } - } + sendEmail = async () => { + const request = await this.buildRequestModel(TwoFactorEmailRequest); + request.email = this.email; + this.emailPromise = this.apiService.postTwoFactorEmailSetup(request); + await this.emailPromise; + this.sentEmail = this.email; + }; protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorEmailRequest); @@ -86,6 +112,10 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { }); } + onClose = () => { + this.dialogRef.close(this.enabled); + }; + private async processResponse(response: TwoFactorEmailResponse) { this.token = null; this.email = response.email; @@ -96,4 +126,15 @@ export class TwoFactorEmailComponent extends TwoFactorBaseComponent { ); } } + /** + * Strongly typed helper to open a TwoFactorEmailComponentComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open( + dialogService: DialogService, + config: DialogConfig>, + ) { + return dialogService.open(TwoFactorEmailComponent, config); + } } diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor-setup.component.html index a20bb345664..28baf72f885 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.html @@ -80,7 +80,6 @@ - diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor-setup.component.ts index dc7871baf94..34a7e32089f 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.ts @@ -1,3 +1,4 @@ +import { DialogRef } from "@angular/cdk/dialog"; import { Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/core"; import { firstValueFrom, lastValueFrom, Observable, Subject, takeUntil } from "rxjs"; @@ -33,8 +34,6 @@ import { TwoFactorYubiKeyComponent } from "./two-factor-yubikey.component"; templateUrl: "two-factor-setup.component.html", }) export class TwoFactorSetupComponent implements OnInit, OnDestroy { - @ViewChild("authenticatorTemplate", { read: ViewContainerRef, static: true }) - authenticatorModalRef: ViewContainerRef; @ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true }) yubikeyModalRef: ViewContainerRef; @ViewChild("duoTemplate", { read: ViewContainerRef, static: true }) duoModalRef: ViewContainerRef; @@ -136,12 +135,11 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const authComp = await this.openModal( - this.authenticatorModalRef, - TwoFactorAuthenticatorComponent, + const authComp: DialogRef = TwoFactorAuthenticatorComponent.open( + this.dialogService, + { data: result }, ); - await authComp.auth(result); - authComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => { + authComp.componentInstance.onChangeStatus.subscribe((enabled: boolean) => { this.updateStatus(enabled, TwoFactorProviderType.Authenticator); }); break; @@ -178,11 +176,14 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { if (!result) { return; } - const emailComp = await this.openModal(this.emailModalRef, TwoFactorEmailComponent); - await emailComp.auth(result); - emailComp.onUpdated.pipe(takeUntil(this.destroy$)).subscribe((enabled: boolean) => { - this.updateStatus(enabled, TwoFactorProviderType.Email); + const authComp: DialogRef = TwoFactorEmailComponent.open(this.dialogService, { + data: result, }); + authComp.componentInstance.onChangeStatus + .pipe(takeUntil(this.destroy$)) + .subscribe((enabled: boolean) => { + this.updateStatus(enabled, TwoFactorProviderType.Email); + }); break; } case TwoFactorProviderType.WebAuthn: { diff --git a/apps/web/src/app/auth/settings/two-factor-verify.component.html b/apps/web/src/app/auth/settings/two-factor-verify.component.html index 283282ccb8e..288a096c23f 100644 --- a/apps/web/src/app/auth/settings/two-factor-verify.component.html +++ b/apps/web/src/app/auth/settings/two-factor-verify.component.html @@ -7,8 +7,8 @@ diff --git a/apps/web/src/app/auth/settings/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor-verify.component.ts index 7dc2847b828..d41efc9b027 100644 --- a/apps/web/src/app/auth/settings/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-verify.component.ts @@ -10,6 +10,7 @@ import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { TwoFactorResponse } from "@bitwarden/common/auth/types/two-factor-response"; import { Verification } from "@bitwarden/common/auth/types/verification"; +import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; @@ -32,6 +33,7 @@ export class TwoFactorVerifyComponent { protected formGroup = new FormGroup({ secret: new FormControl(null), }); + invalidSecret: boolean = false; constructor( @Inject(DIALOG_DATA) protected data: TwoFactorVerifyDialogData, @@ -45,23 +47,30 @@ export class TwoFactorVerifyComponent { } submit = async () => { - let hashedSecret: string; - this.formPromise = this.userVerificationService - .buildRequest(this.formGroup.value.secret) - .then((request) => { - hashedSecret = - this.formGroup.value.secret.type === VerificationType.MasterPassword - ? request.masterPasswordHash - : request.otp; - return this.apiCall(request); - }); + try { + let hashedSecret: string; + this.formPromise = this.userVerificationService + .buildRequest(this.formGroup.value.secret) + .then((request) => { + hashedSecret = + this.formGroup.value.secret.type === VerificationType.MasterPassword + ? request.masterPasswordHash + : request.otp; + return this.apiCall(request); + }); - const response = await this.formPromise; - this.dialogRef.close({ - response: response, - secret: hashedSecret, - verificationType: this.formGroup.value.secret.type, - }); + const response = await this.formPromise; + this.dialogRef.close({ + response: response, + secret: hashedSecret, + verificationType: this.formGroup.value.secret.type, + }); + } catch (e) { + if (e instanceof ErrorResponse && e.statusCode === 400) { + this.invalidSecret = true; + } + throw e; + } }; get dialogTitle(): string { diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html index 120748d4c0d..f57fb7a3510 100644 --- a/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/enterprise-content.component.html @@ -1,34 +1,44 @@ -

The Password Manager Trusted by Millions

-
-

Everything enterprises need out of a password manager:

+

Start your 7-day Enterprise free trial

+
+

+ Bitwarden is the most trusted password manager designed for seamless administration and employee + usability. +

    -
  • Secure password sharing
  • -
  • - Easy, flexible SSO and SCIM integrations +
  • + Instantly and securely share credentials with the groups and individuals who need them +
  • +
  • + Strengthen company-wide security through centralized administrative control and + policies +
  • +
  • + Streamline user onboarding and automate account provisioning with flexible SSO and SCIM + integrations +
  • +
  • + Migrate to Bitwarden in minutes with comprehensive import options +
  • +
  • + Give all Enterprise users the gift of 360º security with a free Families plan
  • -
  • Free families plan for users
  • -
  • Quick import and migration tools
  • -
  • Simple, streamlined user experience
  • -
  • Priority support and trainers
- -
- - - -
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html b/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html index 120748d4c0d..f57fb7a3510 100644 --- a/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/enterprise1-content.component.html @@ -1,34 +1,44 @@ -

The Password Manager Trusted by Millions

-
-

Everything enterprises need out of a password manager:

+

Start your 7-day Enterprise free trial

+
+

+ Bitwarden is the most trusted password manager designed for seamless administration and employee + usability. +

    -
  • Secure password sharing
  • -
  • - Easy, flexible SSO and SCIM integrations +
  • + Instantly and securely share credentials with the groups and individuals who need them +
  • +
  • + Strengthen company-wide security through centralized administrative control and + policies +
  • +
  • + Streamline user onboarding and automate account provisioning with flexible SSO and SCIM + integrations +
  • +
  • + Migrate to Bitwarden in minutes with comprehensive import options +
  • +
  • + Give all Enterprise users the gift of 360º security with a free Families plan
  • -
  • Free families plan for users
  • -
  • Quick import and migration tools
  • -
  • Simple, streamlined user experience
  • -
  • Priority support and trainers
- -
- - - -
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html b/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html index 120748d4c0d..f57fb7a3510 100644 --- a/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/enterprise2-content.component.html @@ -1,34 +1,44 @@ -

The Password Manager Trusted by Millions

-
-

Everything enterprises need out of a password manager:

+

Start your 7-day Enterprise free trial

+
+

+ Bitwarden is the most trusted password manager designed for seamless administration and employee + usability. +

    -
  • Secure password sharing
  • -
  • - Easy, flexible SSO and SCIM integrations +
  • + Instantly and securely share credentials with the groups and individuals who need them +
  • +
  • + Strengthen company-wide security through centralized administrative control and + policies +
  • +
  • + Streamline user onboarding and automate account provisioning with flexible SSO and SCIM + integrations +
  • +
  • + Migrate to Bitwarden in minutes with comprehensive import options +
  • +
  • + Give all Enterprise users the gift of 360º security with a free Families plan
  • -
  • Free families plan for users
  • -
  • Quick import and migration tools
  • -
  • Simple, streamlined user experience
  • -
  • Priority support and trainers
- -
- - - -
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html new file mode 100644 index 00000000000..d1b33eab3a4 --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.html @@ -0,0 +1,11 @@ +
+
+ + third party awards + +
+
diff --git a/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts new file mode 100644 index 00000000000..c23432b67cf --- /dev/null +++ b/apps/web/src/app/auth/trial-initiation/content/logo-badges.component.ts @@ -0,0 +1,7 @@ +import { Component } from "@angular/core"; + +@Component({ + selector: "app-logo-badges", + templateUrl: "logo-badges.component.html", +}) +export class LogoBadgesComponent {} diff --git a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html index d26bbabaef2..f51c370bebd 100644 --- a/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/teams1-content.component.html @@ -1,21 +1,35 @@ -

Start Your Teams Free Trial Now

-
-
$4 per month / per user
-
Annual subscription
-
-
+

Start your 7-day free trial for Teams

+

- Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. + Strengthen business security with an easy-to-use password manager your team will love.

-
    -
  • Collaborate and share securely
  • -
  • Deploy and manage quickly and easily
  • -
  • Access anywhere on any device
  • -
  • Create your account to get started
  • +
      +
    • + Instantly and securely share credentials with the groups and individuals who need them +
    • +
    • + Migrate to Bitwarden in minutes with comprehensive import options +
    • +
    • + Save time and increase productivity with autofill and instant device syncing +
    • +
    • + Enhance security practices across your team with easy user management +
    - - +
    diff --git a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html b/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html index 3145e20d4f8..f51c370bebd 100644 --- a/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html +++ b/apps/web/src/app/auth/trial-initiation/content/teams2-content.component.html @@ -1,17 +1,35 @@ -

    Start Your Free Trial Now

    -
    +

    Start your 7-day free trial for Teams

    +

    - Millions of individuals, teams, and organizations worldwide trust Bitwarden for secure password - storage and sharing. + Strengthen business security with an easy-to-use password manager your team will love.

    -
      -
    • Collaborate and share securely
    • -
    • Deploy and manage quickly and easily
    • -
    • Access anywhere on any device
    • -
    • Create your account to get started
    • +
        +
      • + Instantly and securely share credentials with the groups and individuals who need them +
      • +
      • + Migrate to Bitwarden in minutes with comprehensive import options +
      • +
      • + Save time and increase productivity with autofill and instant device syncing +
      • +
      • + Enhance security practices across your team with easy user management +
      - - +
      diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.component.spec.ts index 9879743a589..a7916ae946d 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 @@ -12,15 +12,16 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; 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 { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { PlanType } from "@bitwarden/common/billing/enums"; -import { ListResponse } from "@bitwarden/common/models/response/list.response"; 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 { RouterService } from "../../core"; import { SharedModule } from "../../shared"; +import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; +import { OrganizationInvite } from "../organization-invite/organization-invite"; import { TrialInitiationComponent } from "./trial-initiation.component"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; @@ -36,12 +37,16 @@ describe("TrialInitiationComponent", () => { let stateServiceMock: MockProxy; let policyApiServiceMock: MockProxy; let policyServiceMock: MockProxy; + let routerServiceMock: MockProxy; + let acceptOrgInviteServiceMock: MockProxy; beforeEach(() => { // only define services directly that we want to mock return values in this component stateServiceMock = mock(); policyApiServiceMock = mock(); policyServiceMock = mock(); + routerServiceMock = mock(); + acceptOrgInviteServiceMock = mock(); // 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 @@ -81,7 +86,11 @@ describe("TrialInitiationComponent", () => { }, { provide: RouterService, - useValue: mock(), + useValue: routerServiceMock, + }, + { + provide: AcceptOrganizationInviteService, + useValue: acceptOrgInviteServiceMock, }, ], schemas: [NO_ERRORS_SCHEMA], // Allows child components to be ignored (such as register component) @@ -100,8 +109,8 @@ describe("TrialInitiationComponent", () => { // These tests demonstrate mocking service calls describe("onInit() enforcedPolicyOptions", () => { - it("should not set enforcedPolicyOptions if state service returns no invite", async () => { - stateServiceMock.getOrganizationInvitation.mockReturnValueOnce(null); + it("should not set enforcedPolicyOptions if there isn't an org invite in deep linked url", async () => { + acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce(null); // Need to recreate component with new service mock fixture = TestBed.createComponent(TrialInitiationComponent); component = fixture.componentInstance; @@ -109,37 +118,31 @@ describe("TrialInitiationComponent", () => { expect(component.enforcedPolicyOptions).toBe(undefined); }); - it("should set enforcedPolicyOptions if state service returns an invite", async () => { + it("should set enforcedPolicyOptions if the deep linked url has an org invite", async () => { // Set up service method mocks - stateServiceMock.getOrganizationInvitation.mockReturnValueOnce( - Promise.resolve({ - organizationId: testOrgId, - token: "token", - email: "testEmail", - organizationUserId: "123", - }), - ); + acceptOrgInviteServiceMock.getOrganizationInvite.mockResolvedValueOnce({ + organizationId: testOrgId, + token: "token", + email: "testEmail", + organizationUserId: "123", + } as OrganizationInvite); policyApiServiceMock.getPoliciesByToken.mockReturnValueOnce( - Promise.resolve({ - data: [ - { - id: "345", - organizationId: testOrgId, - type: 1, - data: [ - { - minComplexity: 4, - minLength: 10, - requireLower: null, - requireNumbers: null, - requireSpecial: null, - requireUpper: null, - }, - ], - enabled: true, + Promise.resolve([ + { + id: "345", + organizationId: testOrgId, + type: 1, + data: { + minComplexity: 4, + minLength: 10, + requireLower: null, + requireNumbers: null, + requireSpecial: null, + requireUpper: null, }, - ], - } as ListResponse), + enabled: true, + }, + ] as Policy[]), ); policyServiceMock.masterPasswordPolicyOptions$.mockReturnValue( of({ 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 52a4120b1a0..d02b2c9e2ea 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 @@ -14,13 +14,14 @@ import { ProductType } from "@bitwarden/common/enums"; import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request"; 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 { OrganizationCreatedEvent, SubscriptionProduct, TrialOrganizationType, } from "../../billing/accounts/trial-initiation/trial-billing-step.component"; +import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; +import { OrganizationInvite } from "../organization-invite/organization-invite"; import { RouterService } from "./../../core/router.service"; import { VerticalStepperComponent } from "./vertical-stepper/vertical-stepper.component"; @@ -121,12 +122,12 @@ export class TrialInitiationComponent implements OnInit, OnDestroy { protected router: Router, private formBuilder: UntypedFormBuilder, private titleCasePipe: TitleCasePipe, - private stateService: StateService, private logService: LogService, private policyApiService: PolicyApiServiceAbstraction, private policyService: PolicyService, private i18nService: I18nService, private routerService: RouterService, + private acceptOrgInviteService: AcceptOrganizationInviteService, ) {} async ngOnInit(): Promise { @@ -180,30 +181,10 @@ export class TrialInitiationComponent implements OnInit, OnDestroy { : "Password Manager trial from marketing website"; }); - const invite = await this.stateService.getOrganizationInvitation(); - if (invite != null) { - try { - const policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - if (policies.data != null) { - this.policies = Policy.fromListResponse(policies); - } - } catch (e) { - this.logService.error(e); - } - } - - if (this.policies != null) { - this.policyService - .masterPasswordPolicyOptions$(this.policies) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPolicyOptions = enforcedPasswordPolicyOptions; - }); + // If there's a deep linked org invite, use it to get the password policies + const orgInvite = await this.acceptOrgInviteService.getOrganizationInvite(); + if (orgInvite != null) { + await this.initPasswordPolicies(orgInvite); } this.orgInfoFormGroup.controls.name.valueChanges @@ -304,5 +285,31 @@ export class TrialInitiationComponent implements OnInit, OnDestroy { } } + private async initPasswordPolicies(invite: OrganizationInvite): Promise { + if (invite == null) { + return; + } + + try { + this.policies = await this.policyApiService.getPoliciesByToken( + invite.organizationId, + invite.token, + invite.email, + invite.organizationUserId, + ); + } catch (e) { + this.logService.error(e); + } + + if (this.policies != null) { + this.policyService + .masterPasswordPolicyOptions$(this.policies) + .pipe(takeUntil(this.destroy$)) + .subscribe((enforcedPasswordPolicyOptions) => { + this.enforcedPolicyOptions = enforcedPasswordPolicyOptions; + }); + } + } + protected readonly SubscriptionProduct = SubscriptionProduct; } diff --git a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts index 6e25e978580..57d982fd00b 100644 --- a/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts +++ b/apps/web/src/app/auth/trial-initiation/trial-initiation.module.ts @@ -24,6 +24,7 @@ import { DefaultContentComponent } from "./content/default-content.component"; import { EnterpriseContentComponent } from "./content/enterprise-content.component"; import { Enterprise1ContentComponent } from "./content/enterprise1-content.component"; import { Enterprise2ContentComponent } from "./content/enterprise2-content.component"; +import { LogoBadgesComponent } from "./content/logo-badges.component"; import { LogoCnet5StarsComponent } from "./content/logo-cnet-5-stars.component"; import { LogoCnetComponent } from "./content/logo-cnet.component"; import { LogoForbesComponent } from "./content/logo-forbes.component"; @@ -69,6 +70,7 @@ import { VerticalStepperModule } from "./vertical-stepper/vertical-stepper.modul CnetTeamsContentComponent, AbmEnterpriseContentComponent, AbmTeamsContentComponent, + LogoBadgesComponent, LogoCnet5StarsComponent, LogoCnetComponent, LogoForbesComponent, diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts index b8cfb47db1a..da62a6812f1 100644 --- a/apps/web/src/app/auth/update-password.component.ts +++ b/apps/web/src/app/auth/update-password.component.ts @@ -1,60 +1,24 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; +import { Component, inject } from "@angular/core"; import { UpdatePasswordComponent as BaseUpdatePasswordComponent } from "@bitwarden/angular/auth/components/update-password.component"; -import { ApiService } from "@bitwarden/common/abstractions/api.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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; -import { DialogService } from "@bitwarden/components"; + +import { RouterService } from "../core"; + +import { AcceptOrganizationInviteService } from "./organization-invite/accept-organization.service"; @Component({ selector: "app-update-password", templateUrl: "update-password.component.html", }) export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { - constructor( - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - policyService: PolicyService, - cryptoService: CryptoService, - messagingService: MessagingService, - apiService: ApiService, - logService: LogService, - stateService: StateService, - userVerificationService: UserVerificationService, - dialogService: DialogService, - kdfConfigService: KdfConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - ) { - super( - router, - i18nService, - platformUtilsService, - passwordGenerationService, - policyService, - cryptoService, - messagingService, - apiService, - stateService, - userVerificationService, - logService, - dialogService, - kdfConfigService, - masterPasswordService, - accountService, - ); + private routerService = inject(RouterService); + private acceptOrganizationInviteService = inject(AcceptOrganizationInviteService); + + override async cancel() { + // clearing the login redirect url so that the user + // does not join the organization if they cancel + await this.routerService.getAndClearLoginRedirectUrl(); + await this.acceptOrganizationInviteService.clearOrganizationInvitation(); + await super.cancel(); } } diff --git a/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts new file mode 100644 index 00000000000..a915d8f8a6c --- /dev/null +++ b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts @@ -0,0 +1,36 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +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 organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const configService = inject(ConfigService); + const organizationService = inject(OrganizationService); + const providerService = inject(ProviderService); + + const consolidatedBillingEnabled = await configService.getFeatureFlag( + FeatureFlag.EnableConsolidatedBilling, + ); + + if (!consolidatedBillingEnabled) { + return true; + } + + const organization = await organizationService.get(route.params.organizationId); + + if (!organization.hasProvider) { + return true; + } + + const provider = await providerService.get(organization.providerId); + + if (!provider) { + return true; + } + + return provider.providerStatus !== ProviderStatusType.Billable; +}; diff --git a/apps/web/src/app/billing/individual/user-subscription.component.html b/apps/web/src/app/billing/individual/user-subscription.component.html index bed41c8dc5c..5f9e6463f1b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.html +++ b/apps/web/src/app/billing/individual/user-subscription.component.html @@ -1,10 +1,10 @@ - {{ "loading" | i18n }} + {{ "loading" | i18n }} -

      {{ "subscriptionPendingCanceled" | i18n }}

      +

      {{ "subscriptionPendingCanceled" | i18n }}

      @@ -39,12 +37,12 @@
      {{ sub.expiration | date: "mediumDate" }}
      {{ "neverExpires" | i18n }}
      -
      -
      +
      +
      {{ "status" | i18n }}
      - {{ (subscription && subscription.status) || "-" }} + {{ (subscription && subscription.status) || "-" }} {{ "pendingCancellation" | i18n }} @@ -61,19 +59,19 @@
      -
      - {{ "details" | i18n }} - - +
      + {{ "details" | i18n }} + +
      - - + - -
      + {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ {{ i.amount | currency: "$" }} {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}{{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}
      + +
      @@ -91,27 +89,9 @@ {{ "launchCloudSubscription" | i18n }}
      -
      -
      - -

      {{ "updateLicense" | i18n }}

      - - -
      -
      -
      +
      -

      {{ "storage" | i18n }}

      -

      {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}

      -
      -
      - {{ storagePercentage / 100 | percent }} -
      -
      +

      {{ "storage" | i18n }}

      +

      + {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }} +

      + -
      -
      - -
      diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index fa21317c180..9b615f3a690 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -20,6 +20,10 @@ import { OffboardingSurveyDialogResultType, openOffboardingSurvey, } from "../shared/offboarding-survey.component"; +import { + UpdateLicenseDialogComponent, + UpdateLicenseDialogResult, +} from "../shared/update-license-dialog.component"; @Component({ templateUrl: "user-subscription.component.html", @@ -131,35 +135,28 @@ export class UserSubscriptionComponent implements OnInit { }); } - updateLicense() { + updateLicense = async () => { if (this.loading) { return; } - this.showUpdateLicense = true; - } - - closeUpdateLicense(load: boolean) { - this.showUpdateLicense = false; - if (load) { - // 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(); + const dialogRef = UpdateLicenseDialogComponent.open(this.dialogService); + const result = await lastValueFrom(dialogRef.closed); + if (result === UpdateLicenseDialogResult.Updated) { + await this.load(); } - } + }; - adjustStorage = (add: boolean) => { - return async () => { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: 4, - add: add, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); - } - }; + adjustStorage = async (add: boolean) => { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: 4, + add: add, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } }; get subscriptionMarkedForCancel() { diff --git a/apps/web/src/app/billing/organizations/download-license.component.html b/apps/web/src/app/billing/organizations/download-license.component.html index 0997462ce92..33a534bacf7 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.html +++ b/apps/web/src/app/billing/organizations/download-license.component.html @@ -1,39 +1,35 @@ -
      -
      - -

      {{ "downloadLicense" | i18n }}

      -
      -
      -
      - - - - + + + {{ "downloadLicense" | i18n }} + +
      +
      + + {{ "enterInstallationId" | i18n }} + + + + + +
      -
      -
      - - -
      + + + + + + 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 88a37a28aab..6b3a93548b4 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -1,50 +1,61 @@ -import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { DialogService } from "@bitwarden/components"; + +export enum DownloadLicenseDialogResult { + Cancelled = "cancelled", + Downloaded = "downloaded", +} +type DownloadLicenseDialogData = { + /** current organization id */ + organizationId: string; +}; @Component({ - selector: "app-download-license", templateUrl: "download-license.component.html", }) -export class DownloadLicenseComponent { - @Input() organizationId: string; - @Output() onDownloaded = new EventEmitter(); - @Output() onCanceled = new EventEmitter(); - - installationId: string; - formPromise: Promise; - +export class DownloadLicenceDialogComponent { + licenseForm = this.formBuilder.group({ + installationId: ["", [Validators.required]], + }); constructor( + @Inject(DIALOG_DATA) protected data: DownloadLicenseDialogData, + private dialogRef: DialogRef, private fileDownloadService: FileDownloadService, - private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, + protected formBuilder: FormBuilder, ) {} - async submit() { - if (this.installationId == null || this.installationId === "") { + submit = async () => { + this.licenseForm.markAllAsTouched(); + const installationId = this.licenseForm.get("installationId").value; + if (installationId == null || installationId === "") { return; } - - try { - this.formPromise = this.organizationApiService.getLicense( - this.organizationId, - this.installationId, - ); - const license = await this.formPromise; - const licenseString = JSON.stringify(license, null, 2); - this.fileDownloadService.download({ - fileName: "bitwarden_organization_license.json", - blobData: licenseString, - }); - this.onDownloaded.emit(); - } catch (e) { - this.logService.error(e); - } - } - - cancel() { - this.onCanceled.emit(); + const license = await this.organizationApiService.getLicense( + this.data.organizationId, + installationId, + ); + const licenseString = JSON.stringify(license, null, 2); + this.fileDownloadService.download({ + fileName: "bitwarden_organization_license.json", + blobData: licenseString, + }); + this.dialogRef.close(DownloadLicenseDialogResult.Downloaded); + }; + /** + * Strongly typed helper to open a DownloadLicenceDialogComponent + * @param dialogService Instance of the dialog service that will be used to open the dialog + * @param config Configuration for the dialog + */ + static open(dialogService: DialogService, config: DialogConfig) { + return dialogService.open(DownloadLicenceDialogComponent, config); } + cancel = () => { + this.dialogRef.close(DownloadLicenseDialogResult.Cancelled); + }; } diff --git a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts index 8ca7226b97d..4af06628754 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts @@ -5,6 +5,7 @@ import { canAccessBillingTab } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationPermissionsGuard } from "../../admin-console/organizations/guards/org-permissions.guard"; +import { organizationIsUnmanaged } from "../../billing/guards/organization-is-unmanaged.guard"; import { WebPlatformUtilsService } from "../../core/web-platform-utils.service"; import { PaymentMethodComponent } from "../shared"; @@ -29,7 +30,7 @@ const routes: Routes = [ { path: "payment-method", component: PaymentMethodComponent, - canActivate: [OrganizationPermissionsGuard], + canActivate: [OrganizationPermissionsGuard, organizationIsUnmanaged], data: { titleId: "paymentMethod", organizationPermissions: (org: Organization) => org.canEditPaymentMethods, @@ -38,7 +39,7 @@ const routes: Routes = [ { path: "history", component: OrgBillingHistoryViewComponent, - canActivate: [OrganizationPermissionsGuard], + canActivate: [OrganizationPermissionsGuard, organizationIsUnmanaged], data: { titleId: "billingHistory", organizationPermissions: (org: Organization) => org.canViewBillingHistory, 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 490ebafbff0..a95efe32e47 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,7 @@ import { AdjustSubscription } from "./adjust-subscription.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; import { BillingSyncKeyComponent } from "./billing-sync-key.component"; import { ChangePlanComponent } from "./change-plan.component"; -import { DownloadLicenseComponent } from "./download-license.component"; +import { DownloadLicenceDialogComponent } from "./download-license.component"; import { OrgBillingHistoryViewComponent } from "./organization-billing-history-view.component"; import { OrganizationBillingRoutingModule } from "./organization-billing-routing.module"; import { OrganizationPlansComponent } from "./organization-plans.component"; @@ -32,7 +32,7 @@ import { SubscriptionStatusComponent } from "./subscription-status.component"; BillingSyncApiKeyComponent, BillingSyncKeyComponent, ChangePlanComponent, - DownloadLicenseComponent, + DownloadLicenceDialogComponent, OrganizationSubscriptionCloudComponent, OrganizationSubscriptionSelfhostComponent, OrgBillingHistoryViewComponent, diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html index 25ac3a7a155..e11cf602ad2 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html @@ -246,13 +246,6 @@ {{ (hasBillingSyncToken ? "manageBillingSync" : "setUpBillingSync") | i18n }}
      -
      - -

      {{ "additionalOptions" | i18n }}

      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 a0db7b5a200..b6282f1e7b1 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 @@ -29,6 +29,7 @@ import { } from "../shared/offboarding-survey.component"; import { BillingSyncApiKeyComponent } from "./billing-sync-api-key.component"; +import { DownloadLicenceDialogComponent } from "./download-license.component"; import { ManageBilling } from "./icons/manage-billing.icon"; import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.component"; @@ -354,8 +355,12 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy this.showChangePlan = false; } - downloadLicense() { - this.showDownloadLicense = !this.showDownloadLicense; + async downloadLicense() { + DownloadLicenceDialogComponent.open(this.dialogService, { + data: { + organizationId: this.organizationId, + }, + }); } async manageBillingSync() { diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 35fe33c7e06..0031cff7755 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -12,6 +12,7 @@ import { PaymentMethodComponent } from "./payment-method.component"; import { PaymentComponent } from "./payment.component"; import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component"; import { TaxInfoComponent } from "./tax-info.component"; +import { UpdateLicenseDialogComponent } from "./update-license-dialog.component"; import { UpdateLicenseComponent } from "./update-license.component"; @NgModule({ @@ -24,6 +25,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; PaymentMethodComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], exports: [ @@ -34,6 +36,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, + UpdateLicenseDialogComponent, OffboardingSurveyComponent, ], }) diff --git a/apps/web/src/app/billing/shared/payment.component.html b/apps/web/src/app/billing/shared/payment.component.html index db6f4f311b1..d76de65e507 100644 --- a/apps/web/src/app/billing/shared/payment.component.html +++ b/apps/web/src/app/billing/shared/payment.component.html @@ -1,163 +1,113 @@ -

      -
      - - -
      -
      - - -
      -
      - - -
      -
      - - -
      -
      - -
      -
      - -
      -
      -
      - Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay -
      -
      - -
      -
      -
      -
      - - +
      + + + + + {{ "creditCard" | i18n }} - - + + + + + {{ "bankAccount" | i18n }} + + + PayPal + + + + + {{ "accountCredit" | i18n }} + + +
      + +
      +
      + +
      +
      +
      + Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay +
      +
      + +
      +
      +
      +
      + + + + +
      +
      -
      -
      - - - - {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} - -
      -
      - - + + + + {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} + +
      + + {{ "routingNumber" | i18n }} + + + + {{ "accountNumber" | i18n }} + + + + {{ "accountHolderName" | i18n }} + + + + + {{ "bankAccountType" | i18n }} + + + + + +
      -
      - - + + +
      +
      + {{ "paypalClickSubmit" | i18n }}
      -
      - - -
      -
      - - -
      -
      -
      - -
      -
      - {{ "paypalClickSubmit" | i18n }} -
      -
      - - - {{ "makeSureEnoughCredit" | i18n }} - - + + + + {{ "makeSureEnoughCredit" | i18n }} + + +
      diff --git a/apps/web/src/app/billing/shared/payment.component.ts b/apps/web/src/app/billing/shared/payment.component.ts index d3b1dde2481..565be9086da 100644 --- a/apps/web/src/app/billing/shared/payment.component.ts +++ b/apps/web/src/app/billing/shared/payment.component.ts @@ -1,4 +1,5 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Subject, takeUntil } from "rxjs"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; @@ -17,23 +18,34 @@ import { SharedModule } from "../../shared"; export class PaymentComponent implements OnInit, OnDestroy { @Input() showMethods = true; @Input() showOptions = true; - @Input() method = PaymentMethodType.Card; @Input() hideBank = false; @Input() hidePaypal = false; @Input() hideCredit = false; @Input() trialFlow = false; + @Input() + set method(value: PaymentMethodType) { + this._method = value; + this.paymentForm?.controls.method.setValue(value, { emitEvent: false }); + } + + get method(): PaymentMethodType { + return this._method; + } + private _method: PaymentMethodType = PaymentMethodType.Card; + private destroy$ = new Subject(); - - bank: any = { - routing_number: null, - account_number: null, - account_holder_name: null, - account_holder_type: "", - currency: "USD", - country: "US", - }; - + protected paymentForm = new FormGroup({ + method: new FormControl(this.method), + bank: new FormGroup({ + routing_number: new FormControl(null, [Validators.required]), + account_number: new FormControl(null, [Validators.required]), + account_holder_name: new FormControl(null, [Validators.required]), + account_holder_type: new FormControl("", [Validators.required]), + currency: new FormControl("USD"), + country: new FormControl("US"), + }), + }); paymentMethodType = PaymentMethodType; private btScript: HTMLScriptElement; @@ -85,7 +97,6 @@ export class PaymentComponent implements OnInit, OnDestroy { invalid: "is-invalid", }; } - async ngOnInit() { if (!this.showOptions) { this.hidePaypal = this.method !== PaymentMethodType.PayPal; @@ -97,6 +108,13 @@ export class PaymentComponent implements OnInit, OnDestroy { if (!this.hidePaypal) { window.document.head.appendChild(this.btScript); } + this.paymentForm + .get("method") + .valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((v) => { + this.method = v; + this.changeMethod(); + }); } ngOnDestroy() { @@ -140,7 +158,6 @@ export class PaymentComponent implements OnInit, OnDestroy { changeMethod() { this.btInstance = null; - if (this.method === PaymentMethodType.PayPal) { window.setTimeout(() => { (window as any).braintree.dropin.create( @@ -209,15 +226,17 @@ export class PaymentComponent implements OnInit, OnDestroy { } }); } else { - this.stripe.createToken("bank_account", this.bank).then((result: any) => { - if (result.error) { - reject(result.error.message); - } else if (result.token && result.token.id != null) { - resolve([result.token.id, this.method]); - } else { - reject(); - } - }); + this.stripe + .createToken("bank_account", this.paymentForm.get("bank").value) + .then((result: any) => { + if (result.error) { + reject(result.error.message); + } else if (result.token && result.token.id != null) { + resolve([result.token.id, this.method]); + } else { + reject(); + } + }); } } }); diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.html b/apps/web/src/app/billing/shared/update-license-dialog.component.html new file mode 100644 index 00000000000..6430c47528f --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.html @@ -0,0 +1,40 @@ +
      + + + + {{ "licenseFile" | i18n }} +
      + + {{ licenseFile ? licenseFile.name : ("noFileChosen" | i18n) }} +
      + + {{ "licenseFileDesc" | i18n: "bitwarden_premium_license.json" }} +
      +
      + + + + +
      +
      diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts new file mode 100644 index 00000000000..5f9a1e94bef --- /dev/null +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -0,0 +1,38 @@ +import { Component } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; + +import { UpdateLicenseComponent } from "./update-license.component"; + +export enum UpdateLicenseDialogResult { + Updated = "updated", + Cancelled = "cancelled", +} +@Component({ + templateUrl: "update-license-dialog.component.html", +}) +export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { + constructor( + apiService: ApiService, + i18nService: I18nService, + platformUtilsService: PlatformUtilsService, + organizationApiService: OrganizationApiServiceAbstraction, + formBuilder: FormBuilder, + ) { + super(apiService, i18nService, platformUtilsService, organizationApiService, formBuilder); + } + async submitLicense() { + await this.submit(); + } + submitLicenseDialog = async () => { + await this.submitLicense(); + }; + static open(dialogService: DialogService) { + return dialogService.open(UpdateLicenseDialogComponent); + } +} diff --git a/apps/web/src/app/common/base.accept.component.ts b/apps/web/src/app/common/base.accept.component.ts index cad1d90088c..7c35751aea6 100644 --- a/apps/web/src/app/common/base.accept.component.ts +++ b/apps/web/src/app/common/base.accept.component.ts @@ -1,11 +1,12 @@ import { Directive, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; -import { Subject } from "rxjs"; +import { Subject, firstValueFrom } from "rxjs"; import { first, switchMap, takeUntil } from "rxjs/operators"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; @Directive() export abstract class BaseAcceptComponent implements OnInit { @@ -25,7 +26,7 @@ export abstract class BaseAcceptComponent implements OnInit { protected platformUtilService: PlatformUtilsService, protected i18nService: I18nService, protected route: ActivatedRoute, - protected stateService: StateService, + protected authService: AuthService, ) {} abstract authedHandler(qParams: Params): Promise; @@ -41,10 +42,10 @@ export abstract class BaseAcceptComponent implements OnInit { ); let errorMessage: string = null; if (!error) { - this.authed = await this.stateService.getIsAuthenticated(); this.email = qParams.email; - if (this.authed) { + const status = await firstValueFrom(this.authService.activeAccountStatus$); + if (status !== AuthenticationStatus.LoggedOut) { try { await this.authedHandler(qParams); } catch (e) { diff --git a/apps/web/src/app/core/router.service.ts b/apps/web/src/app/core/router.service.ts index caebb227337..2944732aee6 100644 --- a/apps/web/src/app/core/router.service.ts +++ b/apps/web/src/app/core/router.service.ts @@ -12,6 +12,14 @@ import { GlobalState, } from "@bitwarden/common/platform/state"; +/** + * Data properties acceptable for use in route objects (see usage in oss-routing.module.ts for example) + */ +export interface DataProperties { + titleId?: string; // sets the title of the current HTML document (shows in browser tab) + doNotSaveUrl?: boolean; // choose to not keep track of the previous URL in memory +} + const DEEP_LINK_REDIRECT_URL = new KeyDefinition(ROUTER_DISK, "deepLinkRedirectUrl", { deserializer: (value: string) => value, }); @@ -92,7 +100,7 @@ export class RouterService { /** * Fetch and clear persisted LoginRedirectUrl if present in state */ - async getAndClearLoginRedirectUrl(): Promise | undefined { + async getAndClearLoginRedirectUrl(): Promise { const persistedPreLoginUrl = await firstValueFrom(this.deepLinkRedirectUrlState.state$); if (!Utils.isNullOrEmpty(persistedPreLoginUrl)) { diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 1ce8d4d2278..757b8220f3a 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -10,7 +10,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { IconModule, LayoutComponent, NavigationModule } from "@bitwarden/components"; import { PaymentMethodWarningsModule } from "../billing/shared"; diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 93d68d31b38..1115a60bf91 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -7,7 +7,9 @@ import { redirectGuard, tdeDecryptionRequiredGuard, UnauthGuard, + unauthGuardFn, } from "@bitwarden/angular/auth/guards"; +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/auth/angular"; import { flagEnabled, Flags } from "../utils/flags"; @@ -17,7 +19,6 @@ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizatio import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component"; import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { SponsoredFamiliesComponent } from "./admin-console/settings/sponsored-families.component"; -import { AcceptOrganizationComponent } from "./auth/accept-organization.component"; import { deepLinkGuard } from "./auth/guards/deep-link.guard"; import { HintComponent } from "./auth/hint.component"; import { LockComponent } from "./auth/lock.component"; @@ -25,6 +26,7 @@ import { LoginDecryptionOptionsComponent } from "./auth/login/login-decryption-o import { LoginViaAuthRequestComponent } from "./auth/login/login-via-auth-request.component"; import { LoginViaWebAuthnComponent } from "./auth/login/login-via-webauthn/login-via-webauthn.component"; import { LoginComponent } from "./auth/login/login.component"; +import { AcceptOrganizationComponent } from "./auth/organization-invite/accept-organization.component"; import { RecoverDeleteComponent } from "./auth/recover-delete.component"; import { RecoverTwoFactorComponent } from "./auth/recover-two-factor.component"; import { RemovePasswordComponent } from "./auth/remove-password.component"; @@ -40,6 +42,8 @@ import { UpdatePasswordComponent } from "./auth/update-password.component"; import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; import { VerifyRecoverDeleteComponent } from "./auth/verify-recover-delete.component"; +import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; +import { DataProperties } from "./core"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; import { DomainRulesComponent } from "./settings/domain-rules.component"; @@ -54,7 +58,7 @@ const routes: Routes = [ { path: "", component: FrontendLayoutComponent, - data: { doNotSaveUrl: true }, + data: { doNotSaveUrl: true } satisfies DataProperties, children: [ { path: "", @@ -66,17 +70,17 @@ const routes: Routes = [ { path: "login-with-device", component: LoginViaAuthRequestComponent, - data: { titleId: "loginWithDevice" }, + data: { titleId: "loginWithDevice" } satisfies DataProperties, }, { path: "login-with-passkey", component: LoginViaWebAuthnComponent, - data: { titleId: "loginWithPasskey" }, + data: { titleId: "loginWithPasskey" } satisfies DataProperties, }, { path: "admin-approval-requested", component: LoginViaAuthRequestComponent, - data: { titleId: "adminApprovalRequested" }, + data: { titleId: "adminApprovalRequested" } satisfies DataProperties, }, { path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] }, { @@ -88,7 +92,7 @@ const routes: Routes = [ path: "register", component: TrialInitiationComponent, canActivate: [UnauthGuard], - data: { titleId: "createAccount" }, + data: { titleId: "createAccount" } satisfies DataProperties, }, { path: "trial", @@ -99,18 +103,18 @@ const routes: Routes = [ path: "sso", component: SsoComponent, canActivate: [UnauthGuard], - data: { titleId: "enterpriseSingleSignOn" }, + data: { titleId: "enterpriseSingleSignOn" } satisfies DataProperties, }, { path: "set-password", component: SetPasswordComponent, - data: { titleId: "setMasterPassword" }, + data: { titleId: "setMasterPassword" } satisfies DataProperties, }, { path: "hint", component: HintComponent, canActivate: [UnauthGuard], - data: { titleId: "passwordHint" }, + data: { titleId: "passwordHint" } satisfies DataProperties, }, { path: "lock", @@ -120,43 +124,28 @@ const routes: Routes = [ { path: "verify-email", component: VerifyEmailTokenComponent }, { path: "accept-organization", + canActivate: [deepLinkGuard()], component: AcceptOrganizationComponent, - canActivate: [deepLinkGuard()], - data: { titleId: "joinOrganization", doNotSaveUrl: false }, - }, - { - path: "accept-emergency", - canActivate: [deepLinkGuard()], - data: { titleId: "acceptEmergency", doNotSaveUrl: false }, - loadComponent: () => - import("./auth/emergency-access/accept/accept-emergency.component").then( - (mod) => mod.AcceptEmergencyComponent, - ), + data: { titleId: "joinOrganization", doNotSaveUrl: false } satisfies DataProperties, }, { path: "accept-families-for-enterprise", component: AcceptFamilySponsorshipComponent, canActivate: [deepLinkGuard()], - data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false }, + data: { titleId: "acceptFamilySponsorship", doNotSaveUrl: false } satisfies DataProperties, }, { path: "recover", pathMatch: "full", redirectTo: "recover-2fa" }, - { - path: "recover-2fa", - component: RecoverTwoFactorComponent, - canActivate: [UnauthGuard], - data: { titleId: "recoverAccountTwoStep" }, - }, { path: "recover-delete", component: RecoverDeleteComponent, canActivate: [UnauthGuard], - data: { titleId: "deleteAccount" }, + data: { titleId: "deleteAccount" } satisfies DataProperties, }, { path: "verify-recover-delete", component: VerifyRecoverDeleteComponent, canActivate: [UnauthGuard], - data: { titleId: "deleteAccount" }, + data: { titleId: "deleteAccount" } satisfies DataProperties, }, { path: "verify-recover-delete-org", @@ -168,30 +157,24 @@ const routes: Routes = [ path: "verify-recover-delete-provider", component: VerifyRecoverDeleteProviderComponent, canActivate: [UnauthGuard], - data: { titleId: "deleteAccount" }, + data: { titleId: "deleteAccount" } satisfies DataProperties, }, { path: "send/:sendId/:key", component: AccessComponent, - data: { title: "Bitwarden Send" }, + data: { titleId: "Bitwarden Send" } satisfies DataProperties, }, { path: "update-temp-password", component: UpdateTempPasswordComponent, canActivate: [AuthGuard], - data: { titleId: "updateTempPassword" }, + data: { titleId: "updateTempPassword" } satisfies DataProperties, }, { path: "update-password", component: UpdatePasswordComponent, canActivate: [AuthGuard], - data: { titleId: "updatePassword" }, - }, - { - path: "remove-password", - component: RemovePasswordComponent, - canActivate: [AuthGuard], - data: { titleId: "removeMasterPassword" }, + data: { titleId: "updatePassword" } satisfies DataProperties, }, { path: "migrate-legacy-encryption", @@ -202,6 +185,63 @@ const routes: Routes = [ }, ], }, + { + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "accept-emergency", + canActivate: [deepLinkGuard()], + children: [ + { + path: "", + data: { + pageTitle: "emergencyAccess", + titleId: "acceptEmergency", + doNotSaveUrl: false, + } satisfies DataProperties & AnonLayoutWrapperData, + loadComponent: () => + import("./auth/emergency-access/accept/accept-emergency.component").then( + (mod) => mod.AcceptEmergencyComponent, + ), + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + }, + { + path: "recover-2fa", + canActivate: [unauthGuardFn()], + children: [ + { + path: "", + component: RecoverTwoFactorComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: "recoverAccountTwoStep", + titleId: "recoverAccountTwoStep", + } satisfies DataProperties & AnonLayoutWrapperData, + }, + { + path: "remove-password", + component: RemovePasswordComponent, + canActivate: [AuthGuard], + data: { + pageTitle: "removeMasterPassword", + titleId: "removeMasterPassword", + } satisfies DataProperties & AnonLayoutWrapperData, + }, + ], + }, { path: "", component: UserLayoutComponent, @@ -211,21 +251,29 @@ const routes: Routes = [ path: "vault", loadChildren: () => VaultModule, }, - { path: "sends", component: SendComponent, data: { titleId: "send" } }, + { + path: "sends", + component: SendComponent, + data: { titleId: "send" } satisfies DataProperties, + }, { path: "create-organization", component: CreateOrganizationComponent, - data: { titleId: "newOrganization" }, + data: { titleId: "newOrganization" } satisfies DataProperties, }, { path: "settings", children: [ { path: "", pathMatch: "full", redirectTo: "account" }, - { path: "account", component: AccountComponent, data: { titleId: "myAccount" } }, + { + path: "account", + component: AccountComponent, + data: { titleId: "myAccount" } satisfies DataProperties, + }, { path: "preferences", component: PreferencesComponent, - data: { titleId: "preferences" }, + data: { titleId: "preferences" } satisfies DataProperties, }, { path: "security", @@ -234,7 +282,7 @@ const routes: Routes = [ { path: "domain-rules", component: DomainRulesComponent, - data: { titleId: "domainRules" }, + data: { titleId: "domainRules" } satisfies DataProperties, }, { path: "subscription", @@ -249,19 +297,19 @@ const routes: Routes = [ { path: "", component: EmergencyAccessComponent, - data: { titleId: "emergencyAccess" }, + data: { titleId: "emergencyAccess" } satisfies DataProperties, }, { path: ":id", component: EmergencyAccessViewComponent, - data: { titleId: "emergencyAccess" }, + data: { titleId: "emergencyAccess" } satisfies DataProperties, }, ], }, { path: "sponsored-families", component: SponsoredFamiliesComponent, - data: { titleId: "sponsoredFamilies" }, + data: { titleId: "sponsoredFamilies" } satisfies DataProperties, }, ], }, @@ -276,7 +324,7 @@ const routes: Routes = [ import("./tools/import/import-web.component").then((mod) => mod.ImportWebComponent), data: { titleId: "importData", - }, + } satisfies DataProperties, }, { path: "export", @@ -286,7 +334,7 @@ const routes: Routes = [ { path: "generator", component: GeneratorComponent, - data: { titleId: "generator" }, + data: { titleId: "generator" } satisfies DataProperties, }, ], }, diff --git a/apps/web/src/app/settings/preferences.component.html b/apps/web/src/app/settings/preferences.component.html index 828c251989f..984ae7536a5 100644 --- a/apps/web/src/app/settings/preferences.component.html +++ b/apps/web/src/app/settings/preferences.component.html @@ -1,106 +1,77 @@ -

      {{ "preferencesDesc" | 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) }} - - - - -
      -
      +

      {{ "preferencesDesc" | 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) }} + + + + -
      - -
      + {{ "vaultTimeoutAction" | i18n }} + - - -
      -
      {{ "lock" | i18n }} + {{ "vaultTimeoutActionLockDesc" | i18n }} + + - - -
      -
      + {{ "logOut" | i18n }} + {{ "vaultTimeoutActionLogOutDesc" | i18n }} + +
      -
      -
      -
      -
      - - - - -
      - - {{ "languageDesc" | i18n }} -
      -
      -
      -
      -
      - - + + {{ "language" | i18n }} + + + + + + + {{ "languageDesc" | i18n }} + + + + {{ "enableFavicon" | i18n }} + -
      - {{ "faviconDesc" | i18n }} -
      -
      -
      -
      - - - {{ "themeDesc" | i18n }} -
      -
      -
      - + + {{ "faviconDesc" | i18n }} + + + {{ "theme" | i18n }} + + + + {{ "themeDesc" | i18n }} + +
      diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index a6443b453ef..1092a31d5c2 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -158,7 +158,7 @@ export class PreferencesComponent implements OnInit { this.form.setValue(initialFormValues, { emitEvent: false }); } - async submit() { + submit = async () => { if (!this.form.controls.vaultTimeout.valid) { this.platformUtilsService.showToast( "error", @@ -188,7 +188,7 @@ export class PreferencesComponent implements OnInit { this.i18nService.t("preferencesUpdated"), ); } - } + }; ngOnDestroy() { this.destroy$.next(); diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index cf273ee6825..b8cfbd34013 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -20,7 +20,6 @@ import { ProvidersComponent } from "../admin-console/providers/providers.compone import { VerifyRecoverDeleteProviderComponent } from "../admin-console/providers/verify-recover-delete-provider.component"; import { SponsoredFamiliesComponent } from "../admin-console/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../admin-console/settings/sponsoring-org-row.component"; -import { AcceptOrganizationComponent } from "../auth/accept-organization.component"; import { HintComponent } from "../auth/hint.component"; import { LockComponent } from "../auth/lock.component"; import { RecoverDeleteComponent } from "../auth/recover-delete.component"; @@ -29,7 +28,7 @@ import { RegisterFormModule } from "../auth/register-form/register-form.module"; import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { AccountComponent } from "../auth/settings/account/account.component"; -import { ChangeAvatarComponent } from "../auth/settings/account/change-avatar.component"; +import { ChangeAvatarDialogComponent } from "../auth/settings/account/change-avatar-dialog.component"; import { ChangeEmailComponent } from "../auth/settings/account/change-email.component"; import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component"; import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component"; @@ -120,7 +119,6 @@ import { SharedModule } from "./shared.module"; ], declarations: [ AcceptFamilySponsorshipComponent, - AcceptOrganizationComponent, AccountComponent, AddEditComponent, AddEditCustomFieldsComponent, @@ -158,7 +156,7 @@ import { SharedModule } from "./shared.module"; PreferencesComponent, PremiumBadgeComponent, ProfileComponent, - ChangeAvatarComponent, + ChangeAvatarDialogComponent, ProvidersComponent, PurgeVaultComponent, RecoverDeleteComponent, @@ -193,7 +191,6 @@ import { SharedModule } from "./shared.module"; exports: [ UserVerificationModule, PremiumBadgeComponent, - AcceptOrganizationComponent, AccountComponent, AddEditComponent, AddEditCustomFieldsComponent, @@ -233,7 +230,7 @@ import { SharedModule } from "./shared.module"; PreferencesComponent, PremiumBadgeComponent, ProfileComponent, - ChangeAvatarComponent, + ChangeAvatarDialogComponent, ProvidersComponent, PurgeVaultComponent, RecoverDeleteComponent, diff --git a/apps/web/src/app/tools/vault-export/export.component.ts b/apps/web/src/app/tools/vault-export/export.component.ts index 7902d2818d6..df53e599ed7 100644 --- a/apps/web/src/app/tools/vault-export/export.component.ts +++ b/apps/web/src/app/tools/vault-export/export.component.ts @@ -42,4 +42,9 @@ export class ExportComponent extends BaseExportComponent { organizationService, ); } + + protected saved() { + super.saved(); + this.platformUtilsService.showToast("success", null, this.i18nService.t("exportSuccess")); + } } diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index b54f2e9c115..1cb8e13cb3e 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -11,9 +11,9 @@ import { KdfType, PBKDF2_ITERATIONS } from "@bitwarden/common/platform/enums"; import { StateProvider, ActiveUserState, - KeyDefinition, PREMIUM_BANNER_DISK_LOCAL, BANNERS_DISMISSED_DISK, + UserKeyDefinition, } from "@bitwarden/common/platform/state"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -33,19 +33,21 @@ type PremiumBannerReprompt = { /** Banners that will be re-shown on a new session */ type SessionBanners = Omit; -export const PREMIUM_BANNER_REPROMPT_KEY = new KeyDefinition( +export const PREMIUM_BANNER_REPROMPT_KEY = new UserKeyDefinition( PREMIUM_BANNER_DISK_LOCAL, "bannerReprompt", { deserializer: (bannerReprompt) => bannerReprompt, + clearOn: [], // Do not clear user tutorials }, ); -export const BANNERS_DISMISSED_DISK_KEY = new KeyDefinition( +export const BANNERS_DISMISSED_DISK_KEY = new UserKeyDefinition( BANNERS_DISMISSED_DISK, "bannersDismissed", { deserializer: (bannersDismissed) => bannersDismissed, + clearOn: [], // Do not clear user tutorials }, ); diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 8dd63e62ddb..5a138c3147b 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -13,7 +13,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; 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/vault/abstractions/sync/sync.service.abstraction"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService } from "@bitwarden/components"; import { OrganizationUserResetPasswordService } from "../../../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service"; diff --git a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts index 927de00737c..4f791a1a2d9 100644 --- a/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-onboarding/services/vault-onboarding.service.ts @@ -3,8 +3,8 @@ import { Observable } from "rxjs"; import { ActiveUserState, - KeyDefinition, StateProvider, + UserKeyDefinition, VAULT_ONBOARDING, } from "@bitwarden/common/platform/state"; @@ -16,9 +16,14 @@ export type VaultOnboardingTasks = { installExtension: boolean; }; -const VAULT_ONBOARDING_KEY = new KeyDefinition(VAULT_ONBOARDING, "tasks", { - deserializer: (jsonData) => jsonData, -}); +const VAULT_ONBOARDING_KEY = new UserKeyDefinition( + VAULT_ONBOARDING, + "tasks", + { + deserializer: (jsonData) => jsonData, + clearOn: [], // do not clear tutorials + }, +); @Injectable() export class VaultOnboardingService implements VaultOnboardingServiceAbstraction { diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index ca04b3aa51f..ae3a0657788 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -45,9 +45,9 @@ 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"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CollectionData } from "@bitwarden/common/vault/models/data/collection.data"; 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 06907b9e5dc..af8789c906c 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.html +++ b/apps/web/src/app/vault/org-vault/vault.component.html @@ -62,7 +62,7 @@ [showPremiumFeatures]="organization?.useTotp" [showBulkMove]="false" [showBulkTrashOptions]="filter.type === 'trash'" - [useEvents]="organization?.useEvents" + [useEvents]="organization?.canAccessEventLogs" [showAdminActions]="true" (onEvent)="onVaultItemsEvent($event)" [showBulkEditCollectionAccess]="organization?.flexibleCollections" diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 514cb8150d1..dfdce5c818e 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -48,10 +48,10 @@ 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"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; 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 869cbaab1b4..9a677af7b5d 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -8,7 +8,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { Verification } from "@bitwarden/common/auth/types/verification"; 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 { SyncService } from "@bitwarden/common/platform/sync"; import { DialogService } from "@bitwarden/components"; export interface PurgeVaultDialogData { diff --git a/apps/web/src/images/register-layout/vault-signup-badges.png b/apps/web/src/images/register-layout/vault-signup-badges.png new file mode 100644 index 00000000000..c8a7ae2f48f Binary files /dev/null and b/apps/web/src/images/register-layout/vault-signup-badges.png differ diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 4556a27dcb9..0fb5cf25f96 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -579,11 +579,17 @@ "message": "Toegang" }, "accessLevel": { - "message": "Access level" + "message": "Toegangsvlak" + }, + "accessing": { + "message": "Accessing" }, "loggedOut": { "message": "Uitgeteken" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "U aantekensessie het verstryk." }, @@ -672,10 +678,10 @@ "message": "Encryption not supported" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "Stel enkripsie op" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "Gebruik vir enkripsie" }, "loginWithPasskeyEnabled": { "message": "Log in with passkey turned on" @@ -1045,7 +1051,13 @@ "message": "Kopieer bevestigingskode" }, "copyUuid": { - "message": "Copy UUID" + "message": "Kopieer UUID" + }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." }, "warning": { "message": "Waarskuwing" @@ -1151,7 +1163,7 @@ "message": "Lengte" }, "passwordMinLength": { - "message": "Minimum password length" + "message": "Minimum wagwoordlengte" }, "uppercase": { "message": "Hoofletters (A-Z)", @@ -3244,6 +3256,27 @@ "device": { "message": "Toestel" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Bekyk" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Selfhuisvesting" }, @@ -6814,19 +6880,19 @@ "message": "Use PIN" }, "useBiometrics": { - "message": "Use biometrics" + "message": "Gebruik biometrie" }, "enterVerificationCodeSentToEmail": { - "message": "Enter the verification code that was sent to your email." + "message": "Voer die bevestigingskakel wat na u e-pos gestuur is, in." }, "resendCode": { - "message": "Resend code" + "message": "Stuur kode weer" }, "memberColumnHeader": { - "message": "Member" + "message": "Lid" }, "groupSlashMemberColumnHeader": { - "message": "Group/Member" + "message": "Groep/lid" }, "selectGroupsAndMembers": { "message": "Kies groepe en lede" @@ -6835,7 +6901,7 @@ "message": "Kies groepe" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "Ingestelde toestemmings vir ’n lid sal toestemmings deur daardie lid se groep vervang." }, "noMembersOrGroupsAdded": { "message": "Geen lede of groepe toegevoeg" @@ -7080,7 +7146,7 @@ "description": "Software Development Kit" }, "createAnAccount": { - "message": "Create an account" + "message": "Skep ’n rekening" }, "createSecret": { "message": "Skep ’n geheim" @@ -7666,7 +7732,7 @@ "message": "Service account access updated" }, "commonImportFormats": { - "message": "Common formats", + "message": "Algemene formate", "description": "Label indicating the most common import formats" }, "maintainYourSubscription": { @@ -7743,7 +7809,7 @@ "message": "Provider Portal" }, "success": { - "message": "Success" + "message": "Sukses" }, "restrictedGroupAccess": { "message": "You cannot add yourself to groups." @@ -7862,7 +7928,7 @@ "description": "A warning shown to the user when their subscription is past due and they pay via invoice." }, "unpaidInvoice": { - "message": "Unpaid invoice", + "message": "Onbetaalde faktuur", "description": "The header of a warning box shown to a user whose subscription is unpaid." }, "toReactivateYourSubscription": { @@ -7877,39 +7943,39 @@ "message": "Machine accounts cannot be created in suspended organizations. Please contact your organization owner for assistance." }, "machineAccount": { - "message": "Machine account", + "message": "Masjienrekening", "description": "A machine user which can be used to automate processes and access secrets in the system." }, "machineAccounts": { - "message": "Machine accounts", + "message": "Masjienrekeninge", "description": "The title for the section that deals with machine accounts." }, "newMachineAccount": { - "message": "New machine account", + "message": "Nuwe masjienrekening", "description": "Title for creating a new machine account." }, "machineAccountsNoItemsMessage": { - "message": "Create a new machine account to get started automating secret access.", + "message": "Skep ’n nuwe masjienrekening om die outomatisering van geheimtoegang te begin.", "description": "Message to encourage the user to start creating machine accounts." }, "machineAccountsNoItemsTitle": { - "message": "Nothing to show yet", + "message": "Nog niks om te wys nie", "description": "Title to indicate that there are no machine accounts to display." }, "deleteMachineAccounts": { - "message": "Delete machine accounts", + "message": "Skrap masjienrekeninge", "description": "Title for the action to delete one or multiple machine accounts." }, "deleteMachineAccount": { - "message": "Delete machine account", + "message": "Skrap masjienrekening", "description": "Title for the action to delete a single machine account." }, "viewMachineAccount": { - "message": "View machine account", + "message": "Bekyk masjienrekening", "description": "Action to view the details of a machine account." }, "deleteMachineAccountDialogMessage": { - "message": "Deleting machine account $MACHINE_ACCOUNT$ is permanent and irreversible.", + "message": "Die skrap van masjienrekening $MACHINE_ACCOUNT$ is permanent en onomkeerbaar.", "placeholders": { "machine_account": { "content": "$1", @@ -7918,10 +7984,10 @@ } }, "deleteMachineAccountsDialogMessage": { - "message": "Deleting machine accounts is permanent and irreversible." + "message": "Die skrap van masjienrekeninge is permanent en onomkeerbaar." }, "deleteMachineAccountsConfirmMessage": { - "message": "Delete $COUNT$ machine accounts", + "message": "Skrap $COUNT$ masjienrekeninge", "placeholders": { "count": { "content": "$1", @@ -7930,48 +7996,48 @@ } }, "deleteMachineAccountToast": { - "message": "Machine account deleted" + "message": "Masjienrekening geskrap" }, "deleteMachineAccountsToast": { - "message": "Machine accounts deleted" + "message": "Masjienrekeninge geskrap" }, "searchMachineAccounts": { - "message": "Search machine accounts", + "message": "Soek masjienrekeninge", "description": "Placeholder text for searching machine accounts." }, "editMachineAccount": { - "message": "Edit machine account", + "message": "Wysig masjienrekening", "description": "Title for editing a machine account." }, "machineAccountName": { - "message": "Machine account name", + "message": "Masjienrekeningnaam", "description": "Label for the name of a machine account" }, "machineAccountCreated": { - "message": "Machine account created", + "message": "Masjienrekening geskep", "description": "Notifies that a new machine account has been created" }, "machineAccountUpdated": { - "message": "Machine account updated", + "message": "Masjienrekening bygewerk", "description": "Notifies that a machine account has been updated" }, "projectMachineAccountsDescription": { - "message": "Grant machine accounts access to this project." + "message": "Verleen masjienrekeninge toegang tot hierdie projek." }, "projectMachineAccountsSelectHint": { - "message": "Type or select machine accounts" + "message": "Tik of kies masjienrekeninge" }, "projectEmptyMachineAccountAccessPolicies": { - "message": "Add machine accounts to grant access" + "message": "Voeg masjienrekeninge toe om toegang te verleen" }, "machineAccountPeopleDescription": { - "message": "Grant groups or people access to this machine account." + "message": "Verleen groepe of mense toegang tot hierdie masjienrekening." }, "machineAccountProjectsDescription": { - "message": "Assign projects to this machine account. " + "message": "Ken projekte toe aan hierdie masjienrekening. " }, "createMachineAccount": { - "message": "Create a machine account" + "message": "Skep ’n masjienrekening" }, "maPeopleWarningMessage": { "message": "Removing people from a machine account does not remove the access tokens they created. For security best practice, it is recommended to revoke access tokens created by people removed from a machine account." @@ -8034,10 +8100,10 @@ "message": "Max potential machine account cost" }, "machineAccountAccessUpdated": { - "message": "Machine account access updated" + "message": "Masjienrekeningtoegang bygewerk" }, "restrictedGroupAccessDesc": { - "message": "You cannot add yourself to a group." + "message": "U kan nie uself tot ’n groep toevoeg nie." }, "unassignedItemsBannerSelfHost": { "message": "Notice: On May 2, 2024, unassigned organization items will no longer be visible in your All Vaults view across devices and will only be accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible." @@ -8057,7 +8123,7 @@ "description": "This will be part of a larger sentence, which will read like so: Assign these items to a collection from the Admin Console to make them visible." }, "deleteProvider": { - "message": "Delete provider" + "message": "Skrap verskaffer" }, "deleteProviderConfirmation": { "message": "Deleting a provider is permanent and irreversible. Enter your master password to confirm the deletion of the provider and all associated data." @@ -8115,13 +8181,13 @@ "message": "Use Bitwarden Secrets Manager SDK in the following programming languages to build your own applications." }, "setUpGithubActions": { - "message": "Set up Github Actions" + "message": "Stel Github Actions op" }, "setUpGitlabCICD": { - "message": "Set up GitLab CI/CD" + "message": "Stel GitLab CI/CD op" }, "setUpAnsible": { - "message": "Set up Ansible" + "message": "Stel Ansible op" }, "cSharpSDKRepo": { "message": "View C# repository" @@ -8151,13 +8217,13 @@ "message": "Create a new client organization to manage as a Provider. Additional seats will be reflected in the next billing cycle." }, "selectAPlan": { - "message": "Select a plan" + "message": "Kies ’n plan" }, "thirtyFivePercentDiscount": { - "message": "35% Discount" + "message": "35% afslag" }, "monthPerMember": { - "message": "month per member" + "message": "maand per lid" }, "seats": { "message": "Seats" @@ -8169,7 +8235,7 @@ "message": "Successfully created new client" }, "noAccess": { - "message": "No access" + "message": "Geen toegang" }, "collectionAdminConsoleManaged": { "message": "This collection is only accessible from the admin console" @@ -8178,10 +8244,10 @@ "message": "Toggle Organization Menu" }, "vaultItemSelect": { - "message": "Select vault item" + "message": "Kies kluisitem" }, "collectionItemSelect": { - "message": "Select collection item" + "message": "Kies versamelingitem" }, "manageBillingFromProviderPortalMessage": { "message": "Manage billing from the Provider Portal" @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Terug na $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Terug", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Verwyder $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8221,10 +8287,10 @@ } }, "viewInfo": { - "message": "View info" + "message": "Bekyk inligting" }, "viewAccess": { - "message": "View access" + "message": "Bekyk toegang" }, "noCollectionsSelected": { "message": "You have not selected any collections." @@ -8242,7 +8308,7 @@ "message": "Organization Seats" }, "providerDiscount": { - "message": "$AMOUNT$% Discount", + "message": "$AMOUNT$% afslag", "placeholders": { "amount": { "content": "$1", @@ -8254,18 +8320,39 @@ "message": "Low KDF iterations. Increase your iterations to improve the security of your account." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "Verander KDF-instellings" }, "secureYourInfrastructure": { - "message": "Secure your infrastructure" + "message": "Beveilig u infrastruktuur" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "Beskerm u gesin of besigheid" }, "upgradeOrganizationCloseSecurityGaps": { "message": "Close security gaps with monitoring reports" }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 2ce0c82901d..6285e62d23f 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "تم تسجيل الخروج" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "انتهت صَلاحِيَة جَلسة الدخول." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "تحذير" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "الجهاز" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "استضافة ذاتية" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 141651d5309..fb661923657 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Müraciət səviyyəsi" }, + "accessing": { + "message": "Müraciət edilir" + }, "loggedOut": { "message": "Çıxış edildi" }, + "loggedOutDesc": { + "message": "Hesabınızdan çıxış etmisiniz." + }, "loginExpired": { "message": "Seansın müddəti bitdi." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID-ni kopyala" }, + "errorRefreshingAccessToken": { + "message": "Müraciət tokeni təzələmə xətası" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Təzələmə tokeni və ya API açarlar tapılmadı. Lütfən çıxış edib yenidən giriş etməyə çalışın." + }, "warning": { "message": "Xəbərdarlıq" }, @@ -1342,7 +1354,7 @@ "message": "Hesabınız bağlandı və bütün əlaqəli verilənlər silindi." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "Təşkilatınızı silmək birdəfəlik prosesdir. Bu əməliyyatın geri dönüşü yoxdur." }, "myAccount": { "message": "Hesabım" @@ -3244,6 +3256,27 @@ "device": { "message": "Cihaz" }, + "creatingAccountOn": { + "message": "Hesab yaradılır" + }, + "checkYourEmail": { + "message": "E-poçtunuzu yoxlayın" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Göndərilən e-poçtdakı keçidi izləyin" + }, + "andContinueCreatingYourAccount": { + "message": "və hesabınızı yaratmağa davam edin." + }, + "noEmail": { + "message": "E-poçt yoxdur?" + }, + "goBack": { + "message": "Geri qayıt" + }, + "toEditYourEmailAddress": { + "message": "və e-poçt ünvanına düzəliş et." + }, "view": { "message": "Bax" }, @@ -3392,7 +3425,7 @@ "message": "Bitwarden hesabınızı silmək üçün tələb göndərdiniz. Təsdiqləmək üçün aşağıdakı düyməyə basın." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "Bitwarden təşkilatınızın silinməsini tələb etdiniz." }, "myOrganization": { "message": "Təşkilatım" @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Faktura sinxr tokenini döndərmək, əvvəlki tokeni yararsız edəcək." }, + "selfHostedServer": { + "message": "öz-özünə sahiblik edən" + }, + "customEnvironment": { + "message": "Özəl mühit" + }, + "selfHostedBaseUrlHint": { + "message": "Şirkət daxili sahiblik edən Bitwarden quraşdırmasının təməl URL-sini qeyd edin. Nümunə: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Qabaqcıl konfiqurasiya üçün hər xidmətin təməl URL-sini müstəqil olaraq qeyd edə bilərsiniz." + }, + "selfHostedEnvFormInvalid": { + "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." + }, + "apiUrl": { + "message": "API server URL-si" + }, + "webVaultUrl": { + "message": "Veb anbar server URL-si" + }, + "identityUrl": { + "message": "Kimlik server URL-si" + }, + "notificationsUrl": { + "message": "Bildiriş server URL-si" + }, + "iconsUrl": { + "message": "İkon server URL-si" + }, + "environmentSaved": { + "message": "Mühit URL-ləri saxlanıldı" + }, "selfHostingTitle": { "message": "Öz-özünə sahiblik etmə" }, @@ -8187,7 +8253,7 @@ "message": "Fakturanı \"Provayder Portalı\"ndan idarə et" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ daxilindəki elementlərə bax", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Geri qayıt: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Geri", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Sil: $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Təkmil izləmə üçün ödənişli plana yüksəldərək təhlükəsizlik üzrə zəif nöqtələri qabaqlayın." + }, + "approveAllRequests": { + "message": "Bütün tələbləri təsdiqlə" + }, + "allLoginRequestsApproved": { + "message": "Bütün giriş tələbləri təsdiqləndi" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Güncəlllənən vergi məlumatı" + }, + "unverified": { + "message": "Doğrulanmayıb" + }, + "verified": { + "message": "Doğrulandı" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 2e6709579d4..6cd2e5c77ea 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Вы выйшлі" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Тэрмін дзеяння вашага сеансу завяршыўся." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Скапіяваць UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Папярэджанне" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Прылада" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Прагляд" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Змяненне токена плацежнай сінхранізацыі прывядзе да скасавання папярэдняга токена." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Уласнае размяшчэнне" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index b551adacdbe..b114f9da8cc 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Ниво на достъп" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Бяхте отписани" }, + "loggedOutDesc": { + "message": "Бяхте отписан(а) от регистрацията си." + }, "loginExpired": { "message": "Сесията ви изтече." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Грешка при опресняването на идентификатора за достъп" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Няма намерен идентификатор за опресняване или ключове за ППИ. Опитайте да се отпишете и да се впишете отново." + }, "warning": { "message": "Внимание" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Устройство" }, + "creatingAccountOn": { + "message": "Създаване на регистрация в" + }, + "checkYourEmail": { + "message": "Проверете е-пощата си" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Последвайте връзката в е-писмото изпратено до" + }, + "andContinueCreatingYourAccount": { + "message": "и продължете със създаването на регистрацията си." + }, + "noEmail": { + "message": "Не сте получили е-писмо?" + }, + "goBack": { + "message": "Върнете се назад" + }, + "toEditYourEmailAddress": { + "message": ", за да редактирате адреса на е-пощата си." + }, "view": { "message": "Изглед" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Пресъздаването на идентификатора за синхронизиране на плащанията ще направи така, че досегашният вече няма да работи." }, + "selfHostedServer": { + "message": "собствен хостинг" + }, + "customEnvironment": { + "message": "Специална среда" + }, + "selfHostedBaseUrlHint": { + "message": "Посочете базовия адрес на Вашата собствена инсталация на Битуорден. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За по-детайлна настройка, може да укажете основния адрес на всяка услуга поотделно." + }, + "selfHostedEnvFormInvalid": { + "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." + }, + "apiUrl": { + "message": "Адрес на сървъра за ППИ" + }, + "webVaultUrl": { + "message": "Адрес на сървъра за трезора по уеб" + }, + "identityUrl": { + "message": "Адрес на сървъра за самоличности" + }, + "notificationsUrl": { + "message": "Адрес на сървъра за известия" + }, + "iconsUrl": { + "message": "Адрес на сървъра за иконки" + }, + "environmentSaved": { + "message": "Адресите на средата са запазени" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8187,7 +8253,7 @@ "message": "Управление на плащанията от Портала за доставчици" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Преглед на елементите в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад към $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,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": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Не допускайте уязвимост в сигурността си като надградите до платен план с подобрено наблюдение." + }, + "approveAllRequests": { + "message": "Одобряване на всички заявки" + }, + "allLoginRequestsApproved": { + "message": "Всички заявки за вписване са одобрени" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Биткойн" + }, + "updatedTaxInformation": { + "message": "Обновена данъчна информация" + }, + "unverified": { + "message": "Непотвърден" + }, + "verified": { + "message": "Потвърден" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index cb4e52f754c..b58391e7546 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index b6521e2899d..fb5cc691cfe 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 1baddec1012..f0a50c38824 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Nivell d'accés" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Sessió tancada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "La vostra sessió ha caducat." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copia UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Advertiment" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Dispositiu" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Mostra" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Girar el token de sincronització de facturació invalidarà el token anterior." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Autoallotjat" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index e314b74b88f..c77dc3bdb96 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Úroveň přístupu" }, + "accessing": { + "message": "Přistupování" + }, "loggedOut": { "message": "Odhlášení" }, + "loggedOutDesc": { + "message": "Byli jste odhlášeni ze svého účtu." + }, "loginExpired": { "message": "Platnost přihlášení vypršela." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Kopírovat UUID" }, + "errorRefreshingAccessToken": { + "message": "Chyba aktualizace přístupového tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nebyly nalezeny žádné obnovovací tokeny nebo API klíče. Zkuste se odhlásit a znovu se přihlásit." + }, "warning": { "message": "Varování" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Zařízení" }, + "creatingAccountOn": { + "message": "Vytváření účtu na" + }, + "checkYourEmail": { + "message": "Zkontrolujte Váš e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Následujte pokyny v e-mailu poslaném na" + }, + "andContinueCreatingYourAccount": { + "message": "a pokračujte ve vytváření Vašeho účtu." + }, + "noEmail": { + "message": "Žádný e-mail?" + }, + "goBack": { + "message": "Zpět" + }, + "toEditYourEmailAddress": { + "message": "pro úpravu Vaší e-mailové adresy." + }, "view": { "message": "Zobrazit" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Měnění fakturačního synchronizačního tokenu zruší platnost předchozího tokenu." }, + "selfHostedServer": { + "message": "vlastní hosting" + }, + "customEnvironment": { + "message": "Vlastní prostředí" + }, + "selfHostedBaseUrlHint": { + "message": "Zadejte základní URL adresu Vaší vlastní hostované aplikace Bitwarden. Příklad: https://bitwarden.spolecnost.cz" + }, + "selfHostedCustomEnvHeader": { + "message": "Pro rozšířená nastavení můžete zadat základní URL adresu každé služby zvlášť." + }, + "selfHostedEnvFormInvalid": { + "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." + }, + "apiUrl": { + "message": "URL API serveru" + }, + "webVaultUrl": { + "message": "URL serveru webového trezoru" + }, + "identityUrl": { + "message": "URL serveru identity" + }, + "notificationsUrl": { + "message": "URL serveru pro oznámení" + }, + "iconsUrl": { + "message": "URL serveru ikon" + }, + "environmentSaved": { + "message": "URL adresy prostředí uloženy" + }, "selfHostingTitle": { "message": "Vlastní hosting" }, @@ -8187,7 +8253,7 @@ "message": "Spravovat fakturaci z portálu poskytovatele" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobrazit položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zpět do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Zpět", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odebrat $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Přechodem na placený plán rozšířeného monitorování získáte náskok před zranitelnostmi zabezpečení." + }, + "approveAllRequests": { + "message": "Schválit všechny požadavky" + }, + "allLoginRequestsApproved": { + "message": "Všechny žádosti o přihlášení byly schváleny" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Aktualizované daňové údaje" + }, + "unverified": { + "message": "Neověřeno" + }, + "verified": { + "message": "Ověřeno" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index a46b09e9961..095e4dabca8 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index f3ebd0a1cb5..ff339e7a39e 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Adgangsniveau" }, + "accessing": { + "message": "Tilgår" + }, "loggedOut": { "message": "Logget ud" }, + "loggedOutDesc": { + "message": "Der er blevet logget ud af kontoen." + }, "loginExpired": { "message": "Login-session udløbet." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Kopiér UUID" }, + "errorRefreshingAccessToken": { + "message": "Adgangstoken genopfriskningsfejl" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Ingen genopfriskningstoken eller API-nøgler fundet. Prøv at logge ud og dernæst ind igen." + }, "warning": { "message": "Advarsel" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Enhed" }, + "creatingAccountOn": { + "message": "Opretter konto på" + }, + "checkYourEmail": { + "message": "Tjek din e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Følg linket i e-mailen sendt til" + }, + "andContinueCreatingYourAccount": { + "message": "og fortsæt med kontooprettelsen." + }, + "noEmail": { + "message": "Ingen e-mail?" + }, + "goBack": { + "message": "Gå tilbage" + }, + "toEditYourEmailAddress": { + "message": "for at redigere e-mailadressen." + }, "view": { "message": "Vis" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotering af faktureringssynk-tokenet vil ugyldiggøre det foregående token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Selvhosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Vær opdateret mod sikkerhedssårbarheder ved at opgradere til en betalt abonnementstype for forbedret monitorering." + }, + "approveAllRequests": { + "message": "Godkend alle anmodninger" + }, + "allLoginRequestsApproved": { + "message": "Alle login-anmodninger godkendt" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Opdaterede momsoplysninger" + }, + "unverified": { + "message": "Ubekræftet" + }, + "verified": { + "message": "Bekræftet" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index ad40ad3e5c4..6931b8345af 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Zugriffsebene" }, + "accessing": { + "message": "Zugriff auf" + }, "loggedOut": { "message": "Ausgeloggt" }, + "loggedOutDesc": { + "message": "Du wurdest von deinem Konto abgemeldet." + }, "loginExpired": { "message": "Ihre Anmeldungsitzung ist abgelaufen." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID kopieren" }, + "errorRefreshingAccessToken": { + "message": "Zugangs-Token Aktualisierungsfehler" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Kein Aktualisierungs-Token oder API-Schlüssel gefunden. Bitte versuche dich ab- und wieder anzumelden." + }, "warning": { "message": "Warnung" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Gerät" }, + "creatingAccountOn": { + "message": "Konto wird erstellt bei" + }, + "checkYourEmail": { + "message": "Überprüfe deine E-Mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Folge dem Link in der E-Mail an" + }, + "andContinueCreatingYourAccount": { + "message": "und fahre mit der Erstellung deines Kontos fort." + }, + "noEmail": { + "message": "Keine E-Mail?" + }, + "goBack": { + "message": "Geh zurück" + }, + "toEditYourEmailAddress": { + "message": ", um deine E-Mail-Adresse zu bearbeiten." + }, "view": { "message": "Anzeigen" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Durch Erneuerung des Rechnungssynchronisierungs-Token wird der vorherige Token ungültig." }, + "selfHostedServer": { + "message": "selbst gehostet" + }, + "customEnvironment": { + "message": "Benutzerdefinierte Umgebung" + }, + "selfHostedBaseUrlHint": { + "message": "Gib die Basis-URL deiner vor Ort gehosteten Bitwarden-Installation an. Beispiel: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Für eine erweiterte Konfiguration kannst du die Basis-URL jedes Dienstes unabhängig voneinander angeben." + }, + "selfHostedEnvFormInvalid": { + "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." + }, + "apiUrl": { + "message": "API Server-URL" + }, + "webVaultUrl": { + "message": "URL des Web-Tresor-Servers" + }, + "identityUrl": { + "message": "URL des Identitätsservers" + }, + "notificationsUrl": { + "message": "URL des Benachrichtigungsservers" + }, + "iconsUrl": { + "message": "URL des Icons-Servers" + }, + "environmentSaved": { + "message": "URLs der Umgebung gespeichert" + }, "selfHostingTitle": { "message": "Selbst gehostet" }, @@ -8187,7 +8253,7 @@ "message": "Rechnungen über das Anbieter-Portal verwalten" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Einträge in $NAME$ anzeigen", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Zurück zu $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Zurück", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ entfernen", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Schütze dich vor Sicherheitsschwachstellen, indem du ein kostenpflichtiges Abo für eine verbesserte Sicherheitskontrolle abschließt." + }, + "approveAllRequests": { + "message": "Alle Anfragen genehmigen" + }, + "allLoginRequestsApproved": { + "message": "Alle Anmeldeanfragen genehmigt" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Steuerinformationen aktualisiert" + }, + "unverified": { + "message": "Nicht verifiziert" + }, + "verified": { + "message": "Verifiziert" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index b8261c5a602..b3dcdb0d94e 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Αποσυνδεθήκατε" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Η περίοδος σύνδεσής σας έχει λήξει." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Προειδοποίηση" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Συσκευή" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Προβολή" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index d4d3fc6d815..61c92ba407a 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken":{ + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc":{ + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -5583,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid" :{ + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8288,5 +8333,29 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" + }, + "viewSecret": { + "message": "View secret" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index ba4f660be6e..75054c1a7de 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 2cfc183bb5a..71226fb6177 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index b06fa977195..3d2e0d1e4d8 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Adiaŭita" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Via seanco eksvalidiĝis." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Averto" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Aparato" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Vidigi" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 0a1164be154..f3fddf45bce 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Nivel de acceso" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Sesión terminada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Tu sesión ha expirado." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copiar UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Advertencia" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Dispositivo" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Ver" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotar el token de sincronización de facturación invalidará el token anterior." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Autoalojamiento" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 62d5dde0a0f..138351449a9 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Välja logitud" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sessioon on aegunud." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Hoiatus" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Seade" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Vaata" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index fa09b67d619..6a5e3782bc0 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Saioa itxita" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Saioa amaitu da." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Kontuz" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Gailua" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Erakutsi" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Fakturazioa sinkronizatzeko tokena berrituz gero, aurreko tokena baliogabetuko da." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Ostatatze propioa" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index c2d67c67d08..fe38a1e7e7e 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "خارج شد" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "نشست ورود شما منقضی شده است." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "کپی UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "هشدار" }, @@ -3244,6 +3256,27 @@ "device": { "message": "دستگاه" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "مشاهده" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "چرخاندن توکن همگام‌سازی صورتحساب، توکن قبلی را باطل می‌کند." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "خود میزبانی" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 8958da9f03c..2347c83d0d5 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Käyttöoikeustaso" }, + "accessing": { + "message": "Avataan" + }, "loggedOut": { "message": "Kirjauduttu ulos" }, + "loggedOutDesc": { + "message": "Sinut on kirjattu ulos tililtäsi." + }, "loginExpired": { "message": "Kirjautumisistuntosi on erääntynyt." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Kopioi UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Varoitus" }, @@ -1342,7 +1354,7 @@ "message": "Tilisi on suljettu ja kaikki siihen liittyvät tiedot on poistettu." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "Organisaation poisto on pysyvä toimenpide, eikä sen peruminen ole mahdollista." }, "myAccount": { "message": "Oma tili" @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families -tilauksella." }, "addons": { - "message": "Laajennukset" + "message": "Lisäykset" }, "premiumAccess": { "message": "Premium-käyttöoikeudet" @@ -3244,6 +3256,27 @@ "device": { "message": "Laite" }, + "creatingAccountOn": { + "message": "Luodaan tili palvelimelle" + }, + "checkYourEmail": { + "message": "Tarkasta sähköpostisi" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Seuraa viestin linkkiä, joka lähetettiin sähköpostitse osoitteeseen" + }, + "andContinueCreatingYourAccount": { + "message": "ja jatka tilin luontia." + }, + "noEmail": { + "message": "Etkö saanut viestiä?" + }, + "goBack": { + "message": "Palaa takaisin" + }, + "toEditYourEmailAddress": { + "message": "muokataksesi sähköpostiosoitettasi." + }, "view": { "message": "Näytä" }, @@ -3392,7 +3425,7 @@ "message": "Olet pyytänyt Bitwarden-tilisi poistoa. Vahvista alla olevalla painikeella." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "Olet pyytänyt Bitwarden-organisaatiosi poistoa." }, "myOrganization": { "message": "Oma organisaatio" @@ -3572,10 +3605,10 @@ "message": "Tilausmuutokset aiheuttavat suhteutettuja laskutusmuutoksia. Jos hiljattain kutsuttujen jäsenten määrä ylittää tilauksesi jäsenpaikkojen määrän, veloitetaan suhteutettu hinta uusista jäsenistä välittömästi." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Lisää käyttäjäpaikkoja" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "ottamalla yhteyttä osoitteella" }, "subscriptionUserSeatsLimitedAutoscale": { "message": "Tilausmuutokset aiheuttavat suhteutettuja laskutusmuutoksia. Jos hiljattain kutsuttujen jäsenten määrä ylittää tilauksesi jäsenpaikkojen määrän, veloitetaan suhteutettu hinta uusista jäsenistä välittömästi, kunnes $MAX$ paikan enimmäismäärä täyttyy.", @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Laskutuksen synkronointitunnisteen uudistus mitätöi edellisen tunnisteen." }, + "selfHostedServer": { + "message": "itse ylläpidetty" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API-palvelimen URL" + }, + "webVaultUrl": { + "message": "Verkkoholvipalvelimen URL" + }, + "identityUrl": { + "message": "Henkilöllisyyspalvelimen URL" + }, + "notificationsUrl": { + "message": "Ilmoituspalvelimen URL" + }, + "iconsUrl": { + "message": "Kuvakepalvelimen URL" + }, + "environmentSaved": { + "message": "Palvelinympäristön URL-osoitteet tallennettiin" + }, "selfHostingTitle": { "message": "Itse ylläpidetty" }, @@ -6601,7 +6667,7 @@ "message": "Myönnä käyttöoikeudet kokoelmiin lisäämällä heidät tähän ryhmään." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "Voit määrittää vain hallitsemiasi kokoelmia." }, "accessAllCollectionsDesc": { "message": "Myönnä käyttöoikeudet kaikkiin nykyisiin ja tuleviin kokoelmiin" @@ -6835,7 +6901,7 @@ "message": "Valitse ryhmät" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "Jäsenkohtaisesti määritetyt käyttöoikeudet korvaavat jäsenen ryhmän määrittämät oikeudet." }, "noMembersOrGroupsAdded": { "message": "Jäseniä tai ryhmiä ei ole lisätty." @@ -7749,7 +7815,7 @@ "message": "Et voi lisätä itseäsi ryhmiin." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "Et voi lisätä itseäsi kokoelmiin." }, "assign": { "message": "Määritä" @@ -8187,7 +8253,7 @@ "message": "Hallitse laskutusta Toimittajaportaaliista" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Näytä kohteen $NAME$ kohteet", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Palaa kohteeseen $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Takaisin", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Poista $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8251,21 +8317,42 @@ } }, "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." + "message": "Alhainen KDF-toistojen määrä. Paranna tilisi suojausta korottamalla määrää." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "Muuta KDF-asetuksia" }, "secureYourInfrastructure": { - "message": "Secure your infrastructure" + "message": "Suojaa infrastruktuurisi" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "Suojaa perheesi tai yrityksesi" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "Sulje suojauksesi aukkoja valvontaraporttien avulla" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "Pysy haavoittuvuuksien edellä tehostamalla valvontaa päivittämällä maksulliseen tilaukseeen." + }, + "approveAllRequests": { + "message": "Hyväksy kaikki pyynnöt" + }, + "allLoginRequestsApproved": { + "message": "Kaikki kirjautumispyynnöt hyväksyttiin" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Päivitetyt verotiedot" + }, + "unverified": { + "message": "Vahvistamaton" + }, + "verified": { + "message": "Vahvistettu" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 72c5b1e733b..b73d6af58ff 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Naka-log out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Nag-expire na ang login session mo." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Babala" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Tanaw" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Ang pag ikot ng token ng pag sync ng pagsingil ay magpapawalang bisa sa nakaraang token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Pag host sa sarili" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 1cfcbe42ca5..1c1191476ee 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Niveau d'accès" }, + "accessing": { + "message": "Accès en cours" + }, "loggedOut": { "message": "Déconnecté" }, + "loggedOutDesc": { + "message": "Vous avez été déconnecté de votre compte." + }, "loginExpired": { "message": "Votre session a expiré." }, @@ -636,7 +642,7 @@ "message": "Utilisez une clé d'accès générée qui vous connectera automatiquement sans mot de passe. La biométrie, comme la reconnaissance faciale ou les empreintes digitales, ou une autre méthode de sécurité FIDO2 vérifiera votre identité." }, "newPasskey": { - "message": "Nouvelle clé d'accès" + "message": "Nouvelle clé d'identification (passkey)" }, "learnMoreAboutPasswordless": { "message": "En savoir plus sur l'identification sans mots de passe" @@ -654,7 +660,7 @@ "message": "Il y a eu un problème lors de la crétion de votre clé d'accès." }, "passkeySuccessfullyCreated": { - "message": "Clé d'accès créée avec succès!" + "message": "Clé d'identification (passkey) créée avec succès !" }, "customPasskeyNameInfo": { "message": "Nommez votre clé d'accès pour vous aider à l'identifier." @@ -690,10 +696,10 @@ } }, "passkeyRemoved": { - "message": "Clé d'accès supprimée" + "message": "Clé d'identification (passkey) retirée" }, "removePasskey": { - "message": "Supprimer la clé d'accès" + "message": "Retirer la clé d'identification (passkey)" }, "removePasskeyInfo": { "message": "Si toutes les clés d'accès sont supprimées, vous ne pourrez pas vous connecter à de nouveaux appareils sans votre mot de passe principal." @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copier l'UUID" }, + "errorRefreshingAccessToken": { + "message": "Erreur d'Actualisation du Jeton d'Accès" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Aucun jeton de rafraîchissement ou clé d'API trouvé. Veuillez essayer de vous déconnecter et de vous reconnecter." + }, "warning": { "message": "Attention" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Appareil" }, + "creatingAccountOn": { + "message": "Création du compte sur" + }, + "checkYourEmail": { + "message": "Vérifiez vos courriels" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Suivez le lien dans le courriel envoyé à" + }, + "andContinueCreatingYourAccount": { + "message": "et continuer à créer votre compte." + }, + "noEmail": { + "message": "Pas courriel?" + }, + "goBack": { + "message": "Revenir en arrière" + }, + "toEditYourEmailAddress": { + "message": "pour modifier votre adresse courriel." + }, "view": { "message": "Voir" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Permuter le Jeton de Synchronisation de Facturation invalidera le jeton précédent." }, + "selfHostedServer": { + "message": "auto-hébergé" + }, + "customEnvironment": { + "message": "Environnement personnalisé" + }, + "selfHostedBaseUrlHint": { + "message": "Spécifiez l'URL de base de votre installation auto-hébergée par Bitwarden. Exemple: https://bitwarden.compagnie.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Pour un configuration avancée. Vous pouvez spécifier l'URL de base indépendamment pour chaque service." + }, + "selfHostedEnvFormInvalid": { + "message": "Vous devez ajouter soit l'URL du Serveur de base, soit au moins un environnement personnalisé." + }, + "apiUrl": { + "message": "URL du serveur API" + }, + "webVaultUrl": { + "message": "URL du serveur du coffre Web" + }, + "identityUrl": { + "message": "URL du serveur d'identité" + }, + "notificationsUrl": { + "message": "URL du serveur de notifications" + }, + "iconsUrl": { + "message": "URL du serveur d’icônes" + }, + "environmentSaved": { + "message": "URLs d'environnement enregistrées" + }, "selfHostingTitle": { "message": "Auto-Hébergement" }, @@ -6113,7 +6179,7 @@ "message": "Ce n'est pas vous ?" }, "pickAnAvatarColor": { - "message": "Choisissez la couleur de votre avatar" + "message": "Choisissez une couleur d'avatar" }, "customizeAvatar": { "message": "Personnaliser l'avatar" @@ -7749,7 +7815,7 @@ "message": "Vous ne pouvez vous ajoutez vous-même aux groupes." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "Vous ne pouvez pas vous ajouter vous-même aux collections." }, "assign": { "message": "Assigner" @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Restez en avance sur les vulnérabilités de sécurité en passant à un plan payant pour une surveillance améliorée." + }, + "approveAllRequests": { + "message": "Approuver toutes les demandes" + }, + "allLoginRequestsApproved": { + "message": "Toutes les demandes de connexion approuvées" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Informations sur les taxes mises à jour" + }, + "unverified": { + "message": "Non vérifié" + }, + "verified": { + "message": "Vérifié" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index c1a1530afa9..6eae24eee0f 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "בוצעה יציאה" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "תוקף החיבור שלך הסתיים." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "אזהרה" }, @@ -3244,6 +3256,27 @@ "device": { "message": "מכשיר" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "צפה" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 7209f1caa98..21cabab4b5f 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index 1122b2e8447..ba98ea021f8 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Odjavljen/a" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesija je istekla." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Kopiraj UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Upozorenje" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Uređaj" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Prikaz" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotiranje tokena za sinkornizaciju naplate će poništiti prethodni token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Vlastiti poslužitelj" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 8e9725b8138..1371ceb262f 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Hozzáférési szint" }, + "accessing": { + "message": "Elérés" + }, "loggedOut": { "message": "Megtörtént a kijelentkezés." }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "A bejelentkezési munkamenet lejárt." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID másolása" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Figyelmeztetés" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Eszköz" }, + "creatingAccountOn": { + "message": "Fiók létrehozása:" + }, + "checkYourEmail": { + "message": "Email cím postaláda ellenőrzése" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kövessük a hivatkozást az elküldött emailben" + }, + "andContinueCreatingYourAccount": { + "message": "és folytassuk a fiók létrehozását." + }, + "noEmail": { + "message": "Nem érkezett email?" + }, + "goBack": { + "message": "Vissza" + }, + "toEditYourEmailAddress": { + "message": "az email cím szerkesztéséhez." + }, "view": { "message": "Megtekintés" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "A Számlázási szinkron vezérjel gördülő váltása érvényteleníti ez előző vezérjelet." }, + "selfHostedServer": { + "message": "saját üzemeltetésű" + }, + "customEnvironment": { + "message": "Egyedi környezet" + }, + "selfHostedBaseUrlHint": { + "message": "Adjuk meg a helyileg tárolt Bitwarden telepítés alap webcímét. Példa: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Speciális konfigurációhoz külön-külön megadhatjuk az egyes szolgáltatások alap webcímét." + }, + "selfHostedEnvFormInvalid": { + "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." + }, + "apiUrl": { + "message": "API szerver webcím" + }, + "webVaultUrl": { + "message": "Webes széf szerver webcím" + }, + "identityUrl": { + "message": "Személyazonosság szerver webcím" + }, + "notificationsUrl": { + "message": "Értesítési szerver webcím" + }, + "iconsUrl": { + "message": "Ikonok szerver webcím" + }, + "environmentSaved": { + "message": "A környezeti webcímek mentésre kerültek." + }, "selfHostingTitle": { "message": "Saját kiszolgáló" }, @@ -8187,7 +8253,7 @@ "message": "A számlázás kezelése a szolgáltatói portálon keresztül" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ elemek megtekintése", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Vissza: $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Vissza", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "$NAME$ eltávolítása", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Összes kérelem jóváhagyása" + }, + "allLoginRequestsApproved": { + "message": "At összes bejelentkezési kérés óváhagyásra került." + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Frissített adó információ" + }, + "unverified": { + "message": "Nem ellenőrzött" + }, + "verified": { + "message": "Ellenőrzött" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index dd95aee577d..c481ca8a095 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Keluar" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesi masuk Anda telah berakhir." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Peringatan" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Perangkat" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Tampilan" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 550cd6e698f..192115c13f9 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Livello di accesso" }, + "accessing": { + "message": "Accedendo a" + }, "loggedOut": { "message": "Uscito" }, + "loggedOutDesc": { + "message": "Sei stato fatto uscire dal tuo account." + }, "loginExpired": { "message": "La tua sessione è scaduta." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copia UUID" }, + "errorRefreshingAccessToken": { + "message": "Errore di aggiornamento del token di accesso" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nessun token di aggiornamento o chiave API trovati. Prova ad uscire ed entrare di nuovo." + }, "warning": { "message": "Attenzione" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Dispositivo" }, + "creatingAccountOn": { + "message": "Creazione account su" + }, + "checkYourEmail": { + "message": "Controlli la tua email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Segui il link nell'email inviata a" + }, + "andContinueCreatingYourAccount": { + "message": "e continua a creare il tuo account." + }, + "noEmail": { + "message": "Nessuna email?" + }, + "goBack": { + "message": "Torna indietro" + }, + "toEditYourEmailAddress": { + "message": "per modificare il tuo indirizzo email." + }, "view": { "message": "Visualizza" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "La rotazione del token di sincronizzazione della fatturazione invaliderà il token precedente." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Ambiente personalizzato" + }, + "selfHostedBaseUrlHint": { + "message": "Specifica lo URL principale della tua installazione self-hosted di Bitwarden. Esempio: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Per la configurazione avanzata, puoi specificare lo URL di base di ciascun servizio in modo indipendente." + }, + "selfHostedEnvFormInvalid": { + "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." + }, + "apiUrl": { + "message": "URL del server API" + }, + "webVaultUrl": { + "message": "URL della cassaforte web" + }, + "identityUrl": { + "message": "URL del server di identità" + }, + "notificationsUrl": { + "message": "URL del server delle notifiche" + }, + "iconsUrl": { + "message": "URL del server di icone" + }, + "environmentSaved": { + "message": "URL dell'ambiente salvati" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8187,7 +8253,7 @@ "message": "Gestisci la fatturazione dal Portale del Fornitore" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizza gli elementi in $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Torna a $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Indietro", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Rimuovi $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stai al passo con le vulnerabilità della sicurezza passando a un piano a pagamento per un monitoraggio più avanzato." + }, + "approveAllRequests": { + "message": "Approva tutte le richieste" + }, + "allLoginRequestsApproved": { + "message": "Tutte le richieste di accesso approvate" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Informazioni fiscali aggiornate" + }, + "unverified": { + "message": "Non verificato" + }, + "verified": { + "message": "Verificato" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 42cb2af8bcc..a1f71ef90b9 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "アクセスレベル" }, + "accessing": { + "message": "アクセス中" + }, "loggedOut": { "message": "ログアウトしました" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "ログインセッションの有効期限が切れています。" }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID をコピー" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "注意" }, @@ -3244,6 +3256,27 @@ "device": { "message": "デバイス" }, + "creatingAccountOn": { + "message": "アカウント作成:" + }, + "checkYourEmail": { + "message": "メールをご確認ください" + }, + "followTheLinkInTheEmailSentTo": { + "message": "アカウントの作成を続けるには" + }, + "andContinueCreatingYourAccount": { + "message": "に送られたメールのリンクを開いてください。" + }, + "noEmail": { + "message": "メールがありませんか?" + }, + "goBack": { + "message": "戻って" + }, + "toEditYourEmailAddress": { + "message": "メールアドレスを編集してください。" + }, "view": { "message": "表示" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "請求同期トークンを更新すると、前のトークンは無効になります。" }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "セルフホスティング" }, @@ -8187,7 +8253,7 @@ "message": "プロバイダーポータルからの請求を管理" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "$NAME$ のアイテムを表示", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "$NAME$ に戻る", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,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": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "監視を強化できる有料プランにアップグレードすると、セキュリティ脆弱性に素早く対応できます。" + }, + "approveAllRequests": { + "message": "すべての要求を承認する" + }, + "allLoginRequestsApproved": { + "message": "すべてのログイン要求を承認しました" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "ビットコイン" + }, + "updatedTaxInformation": { + "message": "更新された税情報" + }, + "unverified": { + "message": "未認証" + }, + "verified": { + "message": "認証済み" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 2dfadbbf2d7..e5697457baf 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "გამოსვლა" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "თქვენი სისტემაში შესვლის სესიას ვადა გაუვიდა." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "გაფრთხილება" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 55776491fed..0ab6aa294dc 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "ಲಾಗ್ ಔಟ್" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "ನಿಮ್ಮ ಲಾಗಿನ್ ಸೆಷನ್ ಅವಧಿ ಮೀರಿದೆ." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "ಎಚ್ಚರಿಕೆ" }, @@ -3244,6 +3256,27 @@ "device": { "message": "ಡಿವೈಸ್" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "ವೀಕ್ಷಣೆ" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index ffafeff4213..4fb8aa34212 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "접근 권한" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "로그아웃됨" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "로그인 세션이 만료되었습니다." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID 복사" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "경고" }, @@ -3244,6 +3256,27 @@ "device": { "message": "기기" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "보기" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index cef0c1a05d8..7a5f85fa918 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Piekļuves līmenis" }, + "accessing": { + "message": "Piekļūst" + }, "loggedOut": { "message": "Atteicies" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Pieteikšanās sesija ir beigusies." }, @@ -833,7 +839,7 @@ "message": "Nederīga galvenā parole" }, "invalidFilePassword": { - "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izdošanas datnes izveidošanas brīdī." + "message": "Nederīga datnes parole, lūgums izmantot to paroli, kas tika ievadīta izgūšanas datnes izveidošanas brīdī." }, "lockNow": { "message": "Aizslēgt" @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Ievietot UUID starpliktuvē" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Brīdinājums" }, @@ -1057,10 +1069,10 @@ "message": "Apstiprināt noslēpumu izgūšanu" }, "exportWarningDesc": { - "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Izdzēst to uzreiz pēc izmantošanas." + "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izgūto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas." }, "exportSecretsWarningDesc": { - "message": "Šī izguve satur noslēpumu datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Izdzēst to uzreiz pēc izmantošanas." + "message": "Šī izguve satur noslēpumu datus nešifrētā veidā. Izgūto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas." }, "encExportKeyWarningDesc": { "message": "Šī izguve šifrē datus ar konta šifrēšanas atslēgu. Ja tā jebkad tiks mainīta, izvadi vajadzētu veikt vēlreiz, jo vairs nebūs iespējams atšifrēt šo datni." @@ -1084,10 +1096,10 @@ "message": "Datnes veids" }, "fileEncryptedExportWarningDesc": { - "message": "Šī datņu izdošana būs aizsargāta ar paroli, un būs nepieciešama datnes parole, lai to atšifrētu." + "message": "Šī datņu izgūšana būs aizsargāta ar paroli, un būs nepieciešama datnes parole, lai to atšifrētu." }, "exportPasswordDescription": { - "message": "Šī parole tiks izmantota, lai izdotu un ievietotu šo datni" + "message": "Šī parole tiks izmantota, lai izgūtu un ievietotu šo datni" }, "confirmMasterPassword": { "message": "Apstiprināt galveno paroli" @@ -1102,13 +1114,13 @@ "message": "Apstiprināt datnes paroli" }, "accountRestrictedOptionDescription": { - "message": "Izmantot konta šifrēšanas atslēgu, kas iegūta no lietotājvārda un galvenās paroles, lai šifrētu izdošanu un atļautu ievietošanu tikai pašreizējā Bitwarden kontā." + "message": "Jāizmanto konta šifrēšanas atslēga, kas iegūta no lietotājvārda un galvenās paroles, lai šifrētu izguvi un atļautu ievietošanu tikai pašreizējā Bitwarden kontā." }, "passwordProtectedOptionDescription": { - "message": "Uzstādīt paroli, lai šifrētu izdošanu un tad to ievietotu jebkurā Bitwarden kontā, izmantojot atšifrēšanas paroli." + "message": "Uzstādīt paroli, lai šifrētu izguvi un tad to ievietotu jebkurā Bitwarden kontā, izmantojot atšifrēšanas paroli." }, "exportTypeHeading": { - "message": "Izdošanas veids" + "message": "Izgūšanas veids" }, "accountRestricted": { "message": "Konts ir ierobežots" @@ -3244,6 +3256,27 @@ "device": { "message": "Ierīce" }, + "creatingAccountOn": { + "message": "Tiek veidots konts" + }, + "checkYourEmail": { + "message": "Jāpārbauda e-pasts" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Jāatver saite, kas tika nosūtīta uz e-pasta adresi" + }, + "andContinueCreatingYourAccount": { + "message": "un jāturpina sava konta izveide." + }, + "noEmail": { + "message": "Nav e-pasta?" + }, + "goBack": { + "message": "Atgriezties" + }, + "toEditYourEmailAddress": { + "message": ", lai labotu savu e-pasta adresi." + }, "view": { "message": "Skatīt" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Norēķinu sinhronizācijas pilnvaras nomaiņa padarīs nederīgu iepriekšējo." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Pašizvietošana" }, @@ -5660,10 +5726,10 @@ "message": "Sesijai iestājās noildze. Lūgums mēģināt pieteikties vēlreiz." }, "exportingPersonalVaultTitle": { - "message": "Izdod personīgo glabātavu" + "message": "Izgūst personīgo glabātavu" }, "exportingOrganizationVaultTitle": { - "message": "Izdod apvienības glabātavu" + "message": "Izgūst apvienības glabātavu" }, "exportingIndividualVaultDescription": { "message": "Tiks izgūti tikai atsevišķi glabātavas vienumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti. Tiks izgūta tikai glabātavas vienumu informācija, un saistītie pielikumi netiks iekļauti.", @@ -5675,7 +5741,7 @@ } }, "exportingOrganizationVaultDesc": { - "message": "Tiks izdota tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.", + "message": "Tiks izgūta tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.", "placeholders": { "organization": { "content": "$1", @@ -8187,7 +8253,7 @@ "message": "Norēķinus var pārvaldīt Nodrošinātāju portālā" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Skatīt $NAME$ vienumus", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Atgriezties uz $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Atpakaļ", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Noņemt $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Apsteidz drošības ievainojamības ar pāriešanu uz maksas plānu pastiprinātai uzraudzībai." + }, + "approveAllRequests": { + "message": "Apstiprināt visus pieprasījumus" + }, + "allLoginRequestsApproved": { + "message": "Visi pieteikšanās pieprasījumi apstiprināti" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 40ef281fd96..e05b1a15918 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "ലോഗ് ഔട്ട് ചെയ്തിരിക്കുന്നു" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "നിങ്ങളുടെ പ്രവർത്തന സമയം കഴിഞ്ഞിരിക്കുന്നു." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "മുന്നറിയിപ്പ്" }, @@ -3244,6 +3256,27 @@ "device": { "message": "ഉപകരണം" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "പ്രദർശനം" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 6d867ab93fc..1ebfb3f0031 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -579,11 +579,17 @@ "message": "Tilgang" }, "accessLevel": { - "message": "Access level" + "message": "Tilgangsnivå" + }, + "accessing": { + "message": "Aksesserer" }, "loggedOut": { "message": "Logget av" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Din innloggingsøkt har utløpt." }, @@ -609,79 +615,79 @@ "message": "Logg på med enhet" }, "loginWithDeviceEnabledNote": { - "message": "Log in with device must be set up in the settings of the Bitwarden app. Need another option?" + "message": "Logg på med enhet må settes opp i Bitwarden-innstillingene. Trenger du et annet alternativ?" }, "loginWithMasterPassword": { "message": "Logg på med hovedpassord" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "Leser Passkey..." }, "readingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "Hold dette vinduet åpent og følg anvisningene fra nettleseren." }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "Bruk en annen innloggingsmetode" }, "loginWithPasskey": { - "message": "Log in with passkey" + "message": "Logg inn med Passkey" }, "invalidPasskeyPleaseTryAgain": { - "message": "Invalid Passkey. Please try again." + "message": "Ugyldig Passkey. Vennligst prøv igjen." }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "2FA for passkeys is not supported. Update the app to log in." + "message": "2FA for Passkeys støttes ikke. Oppdater appen for å logge inn." }, "loginWithPasskeyInfo": { - "message": "Use a generated passkey that will automatically log you in without a password. Biometrics, like facial recognition or fingerprint, or another FIDO2 security method will verify your identity." + "message": "Bruk en generert Passkey som automatisk logger deg inn uten bruk av passord. Biometri, som ansiktsgjenkjenning eller fingeravtrykk, eller en annen FIDO2-sikkerhetsmetode, vil bekrefte identiteten din." }, "newPasskey": { - "message": "New passkey" + "message": "Ny Passkey" }, "learnMoreAboutPasswordless": { - "message": "Learn more about passwordless" + "message": "Lær mer om passordfri løsning" }, "creatingPasskeyLoading": { - "message": "Creating passkey..." + "message": "Oppretter Passkey..." }, "creatingPasskeyLoadingInfo": { - "message": "Keep this window open and follow prompts from your browser." + "message": "Hold dette vinduet åpent og følg anvisningene fra nettleseren." }, "errorCreatingPasskey": { - "message": "Error creating passkey" + "message": "Feil ved oppretting av Passkey" }, "errorCreatingPasskeyInfo": { - "message": "There was a problem creating your passkey." + "message": "Det oppstod et problem under oppretting av Passkeyen." }, "passkeySuccessfullyCreated": { - "message": "Passkey successfully created!" + "message": "Passkey opprettet!" }, "customPasskeyNameInfo": { - "message": "Name your passkey to help you identify it." + "message": "Gi Passkeyen et navn for å hjelpe deg med å identifisere den." }, "useForVaultEncryption": { - "message": "Use for vault encryption" + "message": "Bruk for hvelvkryptering" }, "useForVaultEncryptionInfo": { - "message": "Log in and unlock on supported devices without your master password. Follow the prompts from your browser to finalize setup." + "message": "Logg inn og lås opp på støttede enheter uten hovedpassord. Følg anvisningene fra nettleseren din for å avslutte oppsettet." }, "useForVaultEncryptionErrorReadingPasskey": { - "message": "Error reading passkey. Try again or uncheck this option." + "message": "Feil under lesing av Paskey. Prøv igjen, eller fjern avmerkingen for dette alternativet." }, "encryptionNotSupported": { - "message": "Encryption not supported" + "message": "Kryptering ikke støttet" }, "enablePasskeyEncryption": { - "message": "Set up encryption" + "message": "Sett opp kryptering" }, "usedForEncryption": { - "message": "Used for encryption" + "message": "Brukes til kryptering" }, "loginWithPasskeyEnabled": { - "message": "Log in with passkey turned on" + "message": "Logg inn med Passkey aktivert" }, "passkeySaved": { - "message": "$NAME$ saved", + "message": "$NAME$ lagret", "placeholders": { "name": { "content": "$1", @@ -690,19 +696,19 @@ } }, "passkeyRemoved": { - "message": "Passkey removed" + "message": "Passkey fjernet" }, "removePasskey": { - "message": "Remove passkey" + "message": "Fjern Passkey" }, "removePasskeyInfo": { - "message": "If all passkeys are removed, you will be unable to log into new devices without your master password." + "message": "Hvis alle Passkeys fjernes, vil det ikke være mulig å logge inn på nye enheter uten hovedpassordet ditt." }, "passkeyLimitReachedInfo": { - "message": "Passkey limit reached. Remove a passkey to add another." + "message": "Grense for antall Passkeys er nådd. Fjern en Passkey for å legge til en annen." }, "tryAgain": { - "message": "Try again" + "message": "Prøv igjen" }, "createAccount": { "message": "Opprett en konto" @@ -802,7 +808,7 @@ "message": "En uventet feil har oppstått." }, "expirationDateError": { - "message": "Please select an expiration date that is in the future." + "message": "Vennligst velg en utløpsdato som er frem i tid." }, "emailAddress": { "message": "E-postadresse" @@ -1045,7 +1051,13 @@ "message": "Kopier verifiseringskoden" }, "copyUuid": { - "message": "Copy UUID" + "message": "Kopier UUID" + }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." }, "warning": { "message": "Advarsel" @@ -1054,7 +1066,7 @@ "message": "Bekreft eksport av hvelvet" }, "confirmSecretsExport": { - "message": "Confirm secrets export" + "message": "Bekreft eksport av hemmeligheter" }, "exportWarningDesc": { "message": "Eksporten inneholder dine hvelvdata i ukryptert format. Du bør ikke lagre eller sende den eksporterte filen over usikre tjenester (som e-post). Slett filen umiddelbart etter at du er ferdig med å bruke den." @@ -1072,13 +1084,13 @@ "message": "Eksporter" }, "exportFrom": { - "message": "Export from" + "message": "Eksport fra" }, "exportVault": { "message": "Eksporter hvelvet" }, "exportSecrets": { - "message": "Export secrets" + "message": "Eksporter hemmeligheter" }, "fileFormat": { "message": "Filformat" @@ -3244,6 +3256,27 @@ "device": { "message": "Enhet" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Vis" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotasjon av faktureringssynkroniserings-token vil gjøre den forrige token ugyldig." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Selvhosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 7ff269c6797..30be37fe071 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 1d788322e7b..fac7616a090 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Toegangsniveau" }, + "accessing": { + "message": "Toegang verkrijgen" + }, "loggedOut": { "message": "Uitgelogd" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Je inlogsessie is verlopen." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID kopiëren" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Waarschuwing" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Apparaat" }, + "creatingAccountOn": { + "message": "Account maken bij" + }, + "checkYourEmail": { + "message": "Check je e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Volg de link in de e-mail die verstuurd is naar" + }, + "andContinueCreatingYourAccount": { + "message": "en ga verder met het aanmaken van je account." + }, + "noEmail": { + "message": "Geen e-mail?" + }, + "goBack": { + "message": "Ga terug" + }, + "toEditYourEmailAddress": { + "message": "om je e-mailadres te bewerken." + }, "view": { "message": "Weergeven" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Het roteren van het factureringssynchronisatietoken maakt het vorige token ongeldig." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Zelfgehost" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Alle verzoeken goedkeuren" + }, + "allLoginRequestsApproved": { + "message": "Alle inlogverzoeken goedgekeurd" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 575763b8c73..bc3bd862ed5 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Åtvaring" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Eining" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Syn" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 47f164e9163..be31574e754 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Poziom dostępu" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Wylogowano" }, + "loggedOutDesc": { + "message": "Zostałeś wylogowany z konta." + }, "loginExpired": { "message": "Twoja sesja wygasła." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Skopiuj UUID" }, + "errorRefreshingAccessToken": { + "message": "Błąd podczas odświeżania tokenu" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Nie znaleziono tokenu odświeżającego ani kluczy API. Spróbuj wylogować się i zalogować ponownie." + }, "warning": { "message": "Ostrzeżenie" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Urządzenie" }, + "creatingAccountOn": { + "message": "Tworzenie konta na" + }, + "checkYourEmail": { + "message": "Sprawdź swoją pocztę e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Kliknij łącze w wiadomości e-mail wysłanej do" + }, + "andContinueCreatingYourAccount": { + "message": "i kontynuuj tworzenie konta." + }, + "noEmail": { + "message": "Brak wiadomości e-mail?" + }, + "goBack": { + "message": "Wróć" + }, + "toEditYourEmailAddress": { + "message": "aby edytować swój adres e-mail." + }, "view": { "message": "Widok" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Wymiana tokenu synchronizacji płatności spowoduje unieważnienie poprzedniego tokenu." }, + "selfHostedServer": { + "message": "samodzielnie hostowany" + }, + "customEnvironment": { + "message": "Niestandardowe środowisko" + }, + "selfHostedBaseUrlHint": { + "message": "Określ bazowy adres URL swojej instalacji Bitwarden. Przykład: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Dla zaawansowanych konfiguracji możesz określić podstawowy adres URL niezależnie dla każdej usługi." + }, + "selfHostedEnvFormInvalid": { + "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." + }, + "apiUrl": { + "message": "Adres URL serwera API" + }, + "webVaultUrl": { + "message": "Adres URL serwera sejfu internetowego" + }, + "identityUrl": { + "message": "Adres URL serwera tożsamości" + }, + "notificationsUrl": { + "message": "Adres URL serwera powiadomień" + }, + "iconsUrl": { + "message": "Adres URL serwera ikon" + }, + "environmentSaved": { + "message": "Adresy URL środowiska zostały zapisane" + }, "selfHostingTitle": { "message": "Samodzielnie hostowany" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Bądź na bieżąco z lukami bezpieczeństwa dzięki zmianie na płatny plan z bardziej rozbudowanym monitorowaniem." + }, + "approveAllRequests": { + "message": "Zatwierdź wszystkie prośby" + }, + "allLoginRequestsApproved": { + "message": "Wszystkie prośby o logowanie zatwierdzone" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Zaktualizowane informacje podatkowe" + }, + "unverified": { + "message": "Niezweryfikowane" + }, + "verified": { + "message": "Zweryfikowane" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index b8ac6f03829..81f0fab1496 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Nível de acesso" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Sessão encerrada" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copiar URL" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Aviso" }, @@ -1218,7 +1230,7 @@ "message": "O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. As sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "emailChanged": { - "message": "E-mail Alterado" + "message": "E-mail salvo" }, "logBackIn": { "message": "Por favor, reinicie a sessão." @@ -1233,16 +1245,16 @@ "message": "Senha Mestra Alterada" }, "currentMasterPass": { - "message": "Senha Mestra Atual" + "message": "Senha mestra atual" }, "newMasterPass": { - "message": "Nova Senha Mestra" + "message": "Nova senha mestra" }, "confirmNewMasterPass": { - "message": "Confirme a Nova Senha Mestra" + "message": "Confirme a nova senha mestra" }, "encKeySettings": { - "message": "Configurações da Chave de Criptografia" + "message": "Configurações da chave de criptografia" }, "kdfAlgorithm": { "message": "Algoritmo KDF" @@ -1285,7 +1297,7 @@ "message": "Alterar KDF" }, "encKeySettingsChanged": { - "message": "As Configurações da Chave de Criptografia foram Alteradas" + "message": "As configurações da chave de criptografia foram salvas" }, "dangerZone": { "message": "Zona de perigo" @@ -1306,7 +1318,7 @@ "message": "Todas as Sessões Desautorizadas" }, "purgeVault": { - "message": "Limpar o Cofre" + "message": "Limpar cofre" }, "purgedOrganizationVault": { "message": "Cofre da organização limpado." @@ -1342,7 +1354,7 @@ "message": "A sua conta foi fechada e todos os dados associados foram excluídos." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "A exclusão da organização é permanente. Não pode ser desfeita." }, "myAccount": { "message": "Minha Conta" @@ -1358,7 +1370,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.)" }, "onboardingImportDataDetailsLink": { - "message": "Novo Item", + "message": "Novo item", "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.)" }, "onboardingImportDataDetailsPartTwoNoOrgs": { @@ -1370,7 +1382,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. You may need to wait until your administrator confirms your organization membership." }, "importError": { - "message": "Erro de Importação" + "message": "Erro de importação" }, "importErrorDesc": { "message": "Houve um problema com os dados que você tentou importar. Por favor, resolva os erros listados abaixo em seu arquivo de origem e tente novamente." @@ -1541,7 +1553,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Enforce Bitwarden Two-step Login options for members by using the Two-step Login Policy.'" }, "twoStepLoginPolicy": { - "message": "Política de Login em Duas Etapas" + "message": "Política de login em duas etapas" }, "twoStepLoginOrganizationDuoDesc": { "message": "Para aplicar o Login em Duas Etapas pelo Duo, use as opções abaixo." @@ -1553,7 +1565,7 @@ "message": "Ativar o login em duas etapas pode bloquear você permanentemente da sua conta no Bitwarden. Um código de recuperação permite que você acesse sua conta no caso de não poder mais usar seu provedor de login em duas etapas normalmente (por exemplo, você perde seu dispositivo). O suporte do Bitwarden não poderá ajudá-lo se você perder o acesso à sua conta. Recomendamos que você anote ou imprima o código de recuperação e mantenha-o em um local seguro." }, "viewRecoveryCode": { - "message": "Ver Código de Recuperação" + "message": "Ver código de recuperação" }, "providers": { "message": "Provedores", @@ -1566,7 +1578,7 @@ "message": "Habilitado" }, "restoreAccess": { - "message": "Recuperar Acesso" + "message": "Recuperar acesso" }, "premium": { "message": "Premium", @@ -1576,10 +1588,10 @@ "message": "Assinatura Premium" }, "premiumRequired": { - "message": "Requer Assinatura Premium" + "message": "Requer assinatura premium" }, "premiumRequiredDesc": { - "message": "Uma conta premium é necessária para usar esse recurso." + "message": "É necessário ter uma conta premium para usar esse recurso." }, "youHavePremiumAccess": { "message": "Você tem acesso premium" @@ -1597,7 +1609,7 @@ "message": "Desabilitar" }, "revokeAccess": { - "message": "Revogar Acesso" + "message": "Revogar acesso" }, "twoStepLoginProviderEnabled": { "message": "Este provedor de login em duas etapas está ativado em sua conta." @@ -1708,19 +1720,19 @@ "message": "YubiKeys atualizado" }, "disableAllKeys": { - "message": "Desabilitar Todas as Chaves" + "message": "Desativar todas as chaves" }, "twoFactorDuoDesc": { "message": "Insira as informações do aplicativo Bitwarden no painel do administrador do Duo." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "Id do cliente" }, "twoFactorDuoClientSecret": { - "message": "Client Secret" + "message": "Segredo do cliente" }, "twoFactorDuoApiHostname": { - "message": "Servidor API" + "message": "Nome do domínio da API" }, "twoFactorEmailDesc": { "message": "Siga estas etapas para configurar o login em duas etapas com e-mail:" @@ -1789,7 +1801,7 @@ "message": "Você ainda não habilitou nenhum provedor de login em duas etapas. Depois de ativar um provedor de login em duas etapas, você pode conferir aqui o seu código de recuperação." }, "printCode": { - "message": "Imprimir Código", + "message": "Imprimir código", "description": "Print 2FA recovery code" }, "reports": { @@ -1804,16 +1816,16 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { - "message": "Relatório de Sites Inseguros" + "message": "Relatório de sites inseguros" }, "unsecuredWebsitesReportDesc": { "message": "Usar sites inseguros com o esquema http:// pode ser perigoso. Se o site permitir, você deve sempre acessá-lo utilizando o esquema https:// para que a sua conexão seja criptografada." }, "unsecuredWebsitesFound": { - "message": "Sites Inseguros Encontrados" + "message": "Sites inseguros encontrados" }, "unsecuredWebsitesFoundReportDesc": { - "message": "Nós encontramos $COUNT$ item(ns) no seu cofre com URIs não segura(s). Você deve alterar o esquema de URI para https:// se o site permitir.", + "message": "Encontramos $COUNT$ item(ns) em seu cofre com URIs não segura(s). Se o site permitir, altere o esquema da URI para https://.", "placeholders": { "count": { "content": "$1", @@ -1857,7 +1869,7 @@ "message": "Instruções" }, "exposedPasswordsReport": { - "message": "Relatório de Senhas Expostas" + "message": "Relatório de senhas expostas" }, "exposedPasswordsReportDesc": { "message": "As senhas expostas em um vazamento de dados são alvos fáceis para os atacantes. Altere estas senhas para evitar possíveis invasões." @@ -3244,6 +3256,27 @@ "device": { "message": "Dispositivo" }, + "creatingAccountOn": { + "message": "Criando conta em" + }, + "checkYourEmail": { + "message": "Verifique seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue criando a sua conta." + }, + "noEmail": { + "message": "Sem e-mail?" + }, + "goBack": { + "message": "Voltar" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "view": { "message": "Ver" }, @@ -3392,7 +3425,7 @@ "message": "Você pediu para excluir a sua conta no Bitwarden. Clique no botão abaixo para confirmar." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "Você pediu para excluir a sua organização do Bitwarden." }, "myOrganization": { "message": "Minha Organização" @@ -3572,10 +3605,10 @@ "message": "Ajustes em sua assinatura resultarão em alterações rateadas em seus totais de cobrança. Se os usuários recém-convidados excederem o número de vagas de sua assinatura, você receberá imediatamente uma cobrança proporcional pelos usuários adicionais." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Se você quiser adicionar mais" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { - "message": "seats without the bundled offer, please contact" + "message": "assentos sem a oferta empacotada, por favor contacte" }, "subscriptionUserSeatsLimitedAutoscale": { "message": "Ajustes em sua assinatura resultarão em alterações rateadas em seus totais de cobrança. Se os usuários recém-convidados excederem o número de vagas de sua assinatura, você receberá imediatamente uma cobrança proporcional para os usuários adicionais até que seu limite de $MAX$ de vaga seja atingido.", @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Renovar o Token de Sincronização de Cobrança invalidará o token anterior." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Auto-hospedado" }, @@ -6601,7 +6667,7 @@ "message": "Conceder acesso às coleções adicionando-as a este grupo." }, "restrictedCollectionAssignmentDesc": { - "message": "You can only assign collections you manage." + "message": "Você só pode atribuir coleções que você gerencia." }, "accessAllCollectionsDesc": { "message": "Conceder acesso a todas as coleções atuais e futuras." @@ -6835,7 +6901,7 @@ "message": "Selecione grupos" }, "userPermissionOverrideHelperDesc": { - "message": "Permissions set for a member will replace permissions set by that member's group." + "message": "Permissões definidas para um membro substituirão as permissões definidas pelo grupo desse membro." }, "noMembersOrGroupsAdded": { "message": "Nenhum membro ou grupo adicionado" @@ -7027,7 +7093,7 @@ "message": "Atualize suas configurações de criptografia para atender às novas recomendações de segurança e melhorar a proteção da conta." }, "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": "O processo desconectará você de todas as sessões ativas. Você precisará iniciar a sessão novamente e concluir o login em duas etapas, se houver. Recomendamos exportar seu cofre antes de alterar suas configurações de criptografia para evitar perda de dados." }, "secretsManager": { "message": "Gerenciador de Segredos" @@ -7749,7 +7815,7 @@ "message": "Você não pode se adicionar aos grupos." }, "cannotAddYourselfToCollections": { - "message": "You cannot add yourself to collections." + "message": "Você não pode se adicionar às coleções." }, "assign": { "message": "Atribuir" @@ -8187,7 +8253,7 @@ "message": "Gerenciar faturamento a partir do Portal do Provedor" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Visualizar itens em $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Voltar para $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Voltar", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Remover $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8251,21 +8317,42 @@ } }, "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." + "message": "Iterações baixas do KDF. Aumente as suas iterações para melhorar a segurança da sua conta." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "Atualizar as definições do KDF" }, "secureYourInfrastructure": { - "message": "Secure your infrastructure" + "message": "Proteja sua infraestrutura" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "Proteja sua família ou empresa" }, "upgradeOrganizationCloseSecurityGaps": { - "message": "Close security gaps with monitoring reports" + "message": "Fechar lacunas de segurança com relatórios de monitoramento" }, "upgradeOrganizationCloseSecurityGapsDesc": { - "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + "message": "Fique à frente das vulnerabilidades de segurança atualizando para um plano pago para um monitoramento melhorado." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 4636a2ca073..617e7e67af1 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Nível de acesso" }, + "accessing": { + "message": "A aceder" + }, "loggedOut": { "message": "Sessão terminada" }, + "loggedOutDesc": { + "message": "Foi terminada a sessão da sua conta." + }, "loginExpired": { "message": "A sua sessão expirou." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copiar UUID" }, + "errorRefreshingAccessToken": { + "message": "Erro no acesso ao token de atualização" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Não foi encontrado nenhum token de atualização ou chaves API. Por favor, tente terminar a sessão e voltar a iniciá-la." + }, "warning": { "message": "Aviso" }, @@ -2373,7 +2385,7 @@ "message": "Para criar uma organização alojada no local, é necessário carregar um ficheiro de licença válido." }, "accountEmailMustBeVerified": { - "message": "O endereço de e-mail da sua conta deve ser verificado." + "message": "O endereço de e-mail da sua conta precisa de ser verificado." }, "newOrganizationDesc": { "message": "As organizações permitem-lhe partilhar partes do seu cofre com outras pessoas, bem como gerir utilizadores relacionados para uma entidade específica, como uma família, uma pequena equipa ou uma grande empresa." @@ -3244,6 +3256,27 @@ "device": { "message": "Dispositivo" }, + "creatingAccountOn": { + "message": "A criar conta em" + }, + "checkYourEmail": { + "message": "Verifique o seu e-mail" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Siga o link no e-mail enviado para" + }, + "andContinueCreatingYourAccount": { + "message": "e continue a criação da sua conta." + }, + "noEmail": { + "message": "Não recebeu o e-mail?" + }, + "goBack": { + "message": "Volte atrás" + }, + "toEditYourEmailAddress": { + "message": "para editar o seu endereço de e-mail." + }, "view": { "message": "Ver" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Regenerar o token de sincronização de faturação invalidará o token anterior." }, + "selfHostedServer": { + "message": "auto-hospedado" + }, + "customEnvironment": { + "message": "Ambiente personalizado" + }, + "selfHostedBaseUrlHint": { + "message": "Especifique o URL de base da sua instalação Bitwarden hospedada no local. Exemplo: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Para uma configuração avançada, pode especificar o URL de base de cada serviço de forma independente." + }, + "selfHostedEnvFormInvalid": { + "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." + }, + "apiUrl": { + "message": "URL do servidor da API" + }, + "webVaultUrl": { + "message": "URL do servidor do cofre Web" + }, + "identityUrl": { + "message": "URL do servidor de identidade" + }, + "notificationsUrl": { + "message": "URL do servidor de notificações" + }, + "iconsUrl": { + "message": "URL do servidor de ícones" + }, + "environmentSaved": { + "message": "URLs de ambiente guardados" + }, "selfHostingTitle": { "message": "Auto-hospedagem" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Mantenha-se à frente das vulnerabilidades de segurança atualizando para um plano pago para uma monitorização melhorada." + }, + "approveAllRequests": { + "message": "Aprovar todos os pedidos" + }, + "allLoginRequestsApproved": { + "message": "Todos os pedidos de início de sessão aprovados" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Informações fiscais atualizadas" + }, + "unverified": { + "message": "Não verificado" + }, + "verified": { + "message": "Verificado" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8bdeaf5a44e..d0c7e7a0f77 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Deconectat" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Sesiunea de autentificare a expirat." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Avertisment" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Dispozitiv" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Afișare" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Revocarea tokenului de sincronizare a facturării va invalida tokenul anterior." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Auto-găzduire" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index f0a16363265..51275705380 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Уровень доступа" }, + "accessing": { + "message": "Доступ" + }, "loggedOut": { "message": "Вы вышли из хранилища" }, + "loggedOutDesc": { + "message": "Вы вышли из своего аккаунта." + }, "loginExpired": { "message": "Истек срок действия вашей сессии." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Скопировать UUID" }, + "errorRefreshingAccessToken": { + "message": "Ошибка обновления токена доступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не найдены токен обновления или ключи API. Пожалуйста, попробуйте выполнить выход и повторно авторизоваться." + }, "warning": { "message": "Предупреждение" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Устройство" }, + "creatingAccountOn": { + "message": "Создание аккаунта" + }, + "checkYourEmail": { + "message": "Проверьте свою почту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдите по ссылке из отправленного письма" + }, + "andContinueCreatingYourAccount": { + "message": "и продолжите создание аккаунта." + }, + "noEmail": { + "message": "Нет письма?" + }, + "goBack": { + "message": "Вернуться" + }, + "toEditYourEmailAddress": { + "message": "для изменения адреса email." + }, "view": { "message": "Просмотр" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Смена токена синхронизации биллинга сделает предыдущий токен недействительным." }, + "selfHostedServer": { + "message": "собственный хостинг" + }, + "customEnvironment": { + "message": "Пользовательское окружение" + }, + "selfHostedBaseUrlHint": { + "message": "Укажите базовый URL вашего локального хостинга Bitwarden. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для продвинутой конфигурации можно указать базовый URL каждой службы отдельно." + }, + "selfHostedEnvFormInvalid": { + "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." + }, + "apiUrl": { + "message": "URL API сервера" + }, + "webVaultUrl": { + "message": "URL сервера веб-хранилища" + }, + "identityUrl": { + "message": "URL сервера идентификации" + }, + "notificationsUrl": { + "message": "URL сервера уведомлений" + }, + "iconsUrl": { + "message": "URL сервера значков" + }, + "environmentSaved": { + "message": "URL окружения сохранены" + }, "selfHostingTitle": { "message": "Собственное размещение" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Опережайте уязвимости в системе безопасности, перейдя на платный тарифный план для расширенного мониторинга." + }, + "approveAllRequests": { + "message": "Одобрить все запросы" + }, + "allLoginRequestsApproved": { + "message": "Все запросы на вход одобрены" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Обновление сведений о налогах" + }, + "unverified": { + "message": "Неверифицирован" + }, + "verified": { + "message": "Верифицирован" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 75b03dc8065..e0cd81be1b1 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "අවවාදයයි" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index c1f6cc7e047..1bf83692b6b 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Úroveň prístupu" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Odhlásený" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Platnosť prihlásenia vypršala." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Upozornenie" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Zariadenie" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Zobraziť" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Vlastný hosting" }, @@ -8187,7 +8253,7 @@ "message": "Spravujte fakturáciu cez portál poskytovateľa" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Zobraziť položky v $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Späť do $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,11 @@ } }, "back": { - "message": "Back", + "message": "Späť", "description": "Button text to navigate back" }, "removeItem": { - "message": "Remove $NAME$", + "message": "Odstrániť $NAME$", "description": "Remove a selected option, such as a folder or collection", "placeholders": { "name": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Schváliť všetky žiadosti" + }, + "allLoginRequestsApproved": { + "message": "Všetky žiadosti o prihlásenie schválené" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Aktualizované daňové informácie" + }, + "unverified": { + "message": "Neoverený" + }, + "verified": { + "message": "Overený" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index c419844065e..a14b2beb117 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Odjavljen" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Vaša seja je potekla." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Opozorilo" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium dostop" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json index 9fbe2a038a8..d120e1bb4b8 100644 --- a/apps/web/src/locales/sr/messages.json +++ b/apps/web/src/locales/sr/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Ниво приступа" }, + "accessing": { + "message": "Приступ" + }, "loggedOut": { "message": "Одјављено" }, + "loggedOutDesc": { + "message": "Одјављени сте са свог налога." + }, "loginExpired": { "message": "Ваша сесија је истекла." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Копирај UUID" }, + "errorRefreshingAccessToken": { + "message": "Грешка при освежавању токена приступа" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Није пронађен токен за освежавање или АПИ кључеви. Покушајте да се одјавите и поново пријавите." + }, "warning": { "message": "Упозорење" }, @@ -1342,7 +1354,7 @@ "message": "Ваш налог је затворен и сви повезани подаци су избрисани." }, "deleteOrganizationWarning": { - "message": "Deleting your organization is permanent. It cannot be undone." + "message": "Брисање организације је трајно. Не може се поништити." }, "myAccount": { "message": "Мој налог" @@ -1714,10 +1726,10 @@ "message": "Унесите информације о апликацији Bitwarden из администрације Duo." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "Ид клијента" }, "twoFactorDuoClientSecret": { - "message": "Client Secret" + "message": "Тајна клијента" }, "twoFactorDuoApiHostname": { "message": "API Име хоста" @@ -3244,6 +3256,27 @@ "device": { "message": "Уређај" }, + "creatingAccountOn": { + "message": "Креирај налог на" + }, + "checkYourEmail": { + "message": "Проверите свој имејл" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Пратите везу послатој на" + }, + "andContinueCreatingYourAccount": { + "message": "и наставите са креирањем налога." + }, + "noEmail": { + "message": "Немате имејл?" + }, + "goBack": { + "message": "Ићи назад" + }, + "toEditYourEmailAddress": { + "message": "да измените свој имејл." + }, "view": { "message": "Приказ" }, @@ -3392,7 +3425,7 @@ "message": "Затражили сте да избришете свој Bitwarden рачун. Кликните на доње дугме да бисте потврдили." }, "deleteRecoverOrgConfirmDesc": { - "message": "You have requested to delete your Bitwarden organization." + "message": "Захтевали сте да избришете своју Bitwarden оранизацију." }, "myOrganization": { "message": "Моја организација" @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "личан хостинг" + }, + "customEnvironment": { + "message": "Прилагођено окружење" + }, + "selfHostedBaseUrlHint": { + "message": "Наведите основну УРЛ адресу вашег локалног хостовања Bitwarden-а. Пример: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "За напредну конфигурацију, можете навести основну УРЛ адресу сваке услуге независно." + }, + "selfHostedEnvFormInvalid": { + "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." + }, + "apiUrl": { + "message": "УРЛ АПИ Сервера" + }, + "webVaultUrl": { + "message": "УРЛ сервера Сефа" + }, + "identityUrl": { + "message": "УРЛ сервера идентитета" + }, + "notificationsUrl": { + "message": "УРЛ сервера обавештења" + }, + "iconsUrl": { + "message": "УРЛ сервера иконица" + }, + "environmentSaved": { + "message": "УРЛ адресе окружења су сачуване" + }, "selfHostingTitle": { "message": "Ауто-хстинг" }, @@ -8187,7 +8253,7 @@ "message": "Управљајте наплатом из Provider Portal" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Видети ставке у $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад на $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,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": { @@ -8251,10 +8317,10 @@ } }, "lowKDFIterationsBanner": { - "message": "Low KDF iterations. Increase your iterations to improve the security of your account." + "message": "Ниске KDF итерације. Повећајте број понављања да бисте побољшали безбедност свог налога." }, "changeKDFSettings": { - "message": "Change KDF settings" + "message": "Променити KDF подешавања" }, "secureYourInfrastructure": { "message": "Обезбедите своју инфраструктуру" @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Будите испред безбедносних пропуста надоградњом на плаћени план за побољшано праћење." + }, + "approveAllRequests": { + "message": "Одобри све захтеве" + }, + "allLoginRequestsApproved": { + "message": "Сви захтеви за пријаву су одобрени" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Ажуриране пореске информације" + }, + "unverified": { + "message": "Непроверено" + }, + "verified": { + "message": "Проверено" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 875865d45a0..c7148218b3b 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Odjavljeni ste" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Vaša sesija je istekla." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Upozorenje" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 145e0ea5165..84db5a570ca 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Åtkomstnivå" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Utloggad" }, + "loggedOutDesc": { + "message": "Du har blivit utloggad från ditt konto." + }, "loginExpired": { "message": "Din inloggningssession har upphört." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Kopiera UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Varning" }, @@ -2792,7 +2804,7 @@ "message": "Alla" }, "addAccess": { - "message": "Add Access" + "message": "Lägg till åtkomst" }, "addAccessFilter": { "message": "Add Access Filter" @@ -3098,7 +3110,7 @@ } }, "removeUserIdAccess": { - "message": "Remove $ID$ access", + "message": "Återkalla $ID$ åtkomst", "placeholders": { "id": { "content": "$1", @@ -3125,7 +3137,7 @@ } }, "revokeUserId": { - "message": "Revoke $ID$ access", + "message": "Återkalla $ID$ åtkomst", "placeholders": { "id": { "content": "$1", @@ -3244,6 +3256,27 @@ "device": { "message": "Enhet" }, + "creatingAccountOn": { + "message": "Skapa konto på" + }, + "checkYourEmail": { + "message": "Kolla din e-post" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Följ länken i e-postmeddelandet som skickats till" + }, + "andContinueCreatingYourAccount": { + "message": "och fortsätt att skapa ditt konto." + }, + "noEmail": { + "message": "Ingen e-post?" + }, + "goBack": { + "message": "Gå tillbaka" + }, + "toEditYourEmailAddress": { + "message": "för att redigera din e-postadress." + }, "view": { "message": "Visa" }, @@ -3353,7 +3386,7 @@ "message": "Du kan komma åt denna organisation när en administratör har bekräftat ditt medlemskap. Vi skickar ett e-postmeddelande till dig när det sker." }, "inviteInitAcceptedDesc": { - "message": "You can now access this organization." + "message": "Du har nu tillgång till denna organisation." }, "inviteAcceptFailed": { "message": "Det gick inte att acceptera inbjudan. Be en administratör för organisationen att skicka en ny inbjudan." @@ -3398,13 +3431,13 @@ "message": "Min organisation" }, "organizationInfo": { - "message": "Organization info" + "message": "Organisationsinformation" }, "deleteOrganization": { "message": "Radera organisation" }, "deletingOrganizationContentWarning": { - "message": "Enter the master password to confirm deletion of $ORGANIZATION$ and all associated data. Vault data in $ORGANIZATION$ includes:", + "message": "Ange huvudlösenordet för att bekräfta radering av $ORGANIZATION$ och allt tillhörande data. Valvdata i $ORGANIZATION$ inkluderar:", "placeholders": { "organization": { "content": "$1", @@ -3551,7 +3584,7 @@ } }, "limitSubscription": { - "message": "Limit subscription (optional)" + "message": "Begränsa prenumeration (valfritt)" }, "subscriptionSeats": { "message": "Prenumerationsplatser" @@ -3572,7 +3605,7 @@ "message": "Adjustments to your subscription will result in prorated changes to your billing totals. If newly invited members exceed your subscription seats, you will immediately receive a prorated charge for the additional members." }, "smStandaloneTrialSeatCountUpdateMessageFragment1": { - "message": "If you want to add additional" + "message": "Om du vill lägga till ytterligare" }, "smStandaloneTrialSeatCountUpdateMessageFragment2": { "message": "seats without the bundled offer, please contact" @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8207,7 +8273,7 @@ } }, "back": { - "message": "Back", + "message": "Tillbaka", "description": "Button text to navigate back" }, "removeItem": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Godkänn alla förfrågningar" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 65ef50e2504..b34ccbb9013 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Logged out" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Your login session has expired." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Warning" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index f4dd03c0a28..2f6fcd82645 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Access level" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "ออกจากระบบ" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "เซสชันของคุณหมดอายุแล้ว" }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "คำเตือน" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Device" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index b646c054704..32dcc5f14e1 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Erişim seviyesi" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Çıkış yapıldı" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Oturumunuz zaman aşımına uğradı." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "UUID'yi kopyala" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Uyarı" }, @@ -3244,6 +3256,27 @@ "device": { "message": "Cihaz" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "Görüntüle" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Faturalandırma Senkronizasyonu Anahtarını yenilemek, önceki anahtarı geçersiz kılar." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Barındırılan" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index d4bb72dc856..0bbd19b8b80 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Рівень доступу" }, + "accessing": { + "message": "Доступ" + }, "loggedOut": { "message": "Ви вийшли" }, + "loggedOutDesc": { + "message": "Ви вийшли з облікового запису." + }, "loginExpired": { "message": "Тривалість вашого сеансу завершилась." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Копіювати UUID" }, + "errorRefreshingAccessToken": { + "message": "Помилка оновлення токена доступу" + }, + "errorRefreshingAccessTokenDesc": { + "message": "Не знайдено токен оновлення або ключі API. Спробуйте вийти, а потім увійти знову." + }, "warning": { "message": "Попередження" }, @@ -2370,7 +2382,7 @@ "message": "Щоб оновити обліковий запис до Premium, вам необхідно вивантажити дійсний файл ліцензії." }, "uploadLicenseFileOrg": { - "message": "Для створення організації з локальним хостингом вам необхідно вивантажити дійсний файл ліцензії." + "message": "Щоб створити організацію, розміщену на локальному хостингу, вам необхідно вивантажити дійсний файл ліцензії." }, "accountEmailMustBeVerified": { "message": "Необхідно підтвердити адресу електронної пошти вашого облікового запису." @@ -3244,6 +3256,27 @@ "device": { "message": "Пристрій" }, + "creatingAccountOn": { + "message": "Створення облікового запису" + }, + "checkYourEmail": { + "message": "Перевірте свою електронну пошту" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Перейдіть за посиланням у листі, надісланому на" + }, + "andContinueCreatingYourAccount": { + "message": "і завершіть створення облікового запису." + }, + "noEmail": { + "message": "Не отримали електронного листа?" + }, + "goBack": { + "message": "Поверніться назад" + }, + "toEditYourEmailAddress": { + "message": "і виправте адресу електронної пошти." + }, "view": { "message": "Переглянути" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Оновлення токена платіжної синхронізації призведе до скасування попереднього токена." }, + "selfHostedServer": { + "message": "власне розміщення" + }, + "customEnvironment": { + "message": "Власне середовище" + }, + "selfHostedBaseUrlHint": { + "message": "Вкажіть основну URL-адресу вашого локально розміщеного встановлення Bitwarden. Зразок: https://bitwarden.company.com" + }, + "selfHostedCustomEnvHeader": { + "message": "Для розширеної конфігурації ви можете вказати основну URL-адресу окремо для кожної служби." + }, + "selfHostedEnvFormInvalid": { + "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." + }, + "apiUrl": { + "message": "URL-адреса сервера API" + }, + "webVaultUrl": { + "message": "URL-адреса сервера веб сховища" + }, + "identityUrl": { + "message": "URL-адреса сервера ідентифікації" + }, + "notificationsUrl": { + "message": "URL-адреса сервера сповіщень" + }, + "iconsUrl": { + "message": "URL-адреса сервера піктограм" + }, + "environmentSaved": { + "message": "URL-адреси середовища збережено" + }, "selfHostingTitle": { "message": "Власне розміщення" }, @@ -8187,7 +8253,7 @@ "message": "Керування рахунками на порталі провайдера" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "Переглянути записи в $NAME$", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Назад до $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,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": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Попереджайте вразливості безпеки, передплативши тарифний план для посиленого моніторингу." + }, + "approveAllRequests": { + "message": "Схвалити всі запити" + }, + "allLoginRequestsApproved": { + "message": "Усі запити на вхід схвалено" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Податкову інформацію оновлено" + }, + "unverified": { + "message": "Не перевірений" + }, + "verified": { + "message": "Перевірений" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index f3a545c50b9..47d69033e0d 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "Cấp độ truy cập" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "Đã đăng xuất" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "Phiên đăng nhập của bạn đã hết hạn." }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "Copy UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "Cảnh báo" }, @@ -2102,7 +2114,7 @@ "message": "Bitwarden Families plan." }, "addons": { - "message": "Addons" + "message": "Add-ons" }, "premiumAccess": { "message": "Premium access" @@ -3244,6 +3256,27 @@ "device": { "message": "Thiết bị" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "View" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "Rotating the billing sync token will invalidate the previous token." }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "Self-hosting" }, @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 367c8cdd856..535c2abb7f3 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "访问权限等级" }, + "accessing": { + "message": "访问中" + }, "loggedOut": { "message": "已注销" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登录会话已过期。" }, @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "复制 UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "警告" }, @@ -1723,10 +1735,10 @@ "message": "API 主机名" }, "twoFactorEmailDesc": { - "message": "按照以下步骤设置使用电子邮件两步登录:" + "message": "按照以下步骤设置使用电子邮件的两步登录:" }, "twoFactorEmailEnterEmail": { - "message": "输入您希望接收验证码的电子邮件" + "message": "输入您希望接收验证码的电子邮件地址" }, "twoFactorEmailEnterCode": { "message": "输入电子邮件中的 6 位数验证码" @@ -2102,7 +2114,7 @@ "message": "Bitwarden 家庭版计划。" }, "addons": { - "message": "附加项目" + "message": "插件" }, "premiumAccess": { "message": "高级会员" @@ -2540,7 +2552,7 @@ "message": "使用审核日志跟踪用户操作" }, "enforce2faDuo": { - "message": "强制启用 Duo 的两步登录" + "message": "强制 Duo 2FA" }, "priorityCustomerSupport": { "message": "优先客户支持" @@ -2849,7 +2861,7 @@ "message": "停用了两步登录" }, "recovered2fa": { - "message": "从两步登录中恢复了帐户。" + "message": "从两步登录中恢复了账户" }, "failedLogin": { "message": "登录失败,密码不正确。" @@ -3244,6 +3256,27 @@ "device": { "message": "设备" }, + "creatingAccountOn": { + "message": "创建账户于" + }, + "checkYourEmail": { + "message": "检查您的电子邮箱" + }, + "followTheLinkInTheEmailSentTo": { + "message": "点击发送到电子邮件中的链接" + }, + "andContinueCreatingYourAccount": { + "message": "然后继续创建您的账户。" + }, + "noEmail": { + "message": "没收到电子邮件吗?" + }, + "goBack": { + "message": "返回" + }, + "toEditYourEmailAddress": { + "message": "编辑您的电子邮件地址。" + }, "view": { "message": "查看" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "轮换计费同步令牌将使之前的令牌失效。" }, + "selfHostedServer": { + "message": "自托管" + }, + "customEnvironment": { + "message": "自定义环境" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API 服务器 URL" + }, + "webVaultUrl": { + "message": "网页密码库服务器 URL" + }, + "identityUrl": { + "message": "身份服务器 URL" + }, + "notificationsUrl": { + "message": "通知服务器 URL" + }, + "iconsUrl": { + "message": "图标服务器 URL" + }, + "environmentSaved": { + "message": "环境 URL 已保存" + }, "selfHostingTitle": { "message": "自托管" }, @@ -6110,7 +6176,7 @@ "message": "正登录为" }, "notYou": { - "message": "不是你?" + "message": "不是您吗?" }, "pickAnAvatarColor": { "message": "选择头像颜色" @@ -6835,7 +6901,7 @@ "message": "选择群组" }, "userPermissionOverrideHelperDesc": { - "message": "为成员设置的权限将替换为该成员群组设置的权限。" + "message": "为成员设置的权限将替换为由该成员群组设置的权限。" }, "noMembersOrGroupsAdded": { "message": "未添加任何成员或群组" @@ -7590,7 +7656,7 @@ "message": "别名域" }, "alreadyHaveAccount": { - "message": "已经有一个账户?" + "message": "已经拥有账户了吗?" }, "skipToContent": { "message": "跳转到内容" @@ -8166,7 +8232,7 @@ "message": "添加组织" }, "createdNewClient": { - "message": "Successfully created new client" + "message": "成功创建了新的客户" }, "noAccess": { "message": "无访问权限" @@ -8187,7 +8253,7 @@ "message": "从提供商门户管理账单" }, "viewItemsIn": { - "message": "View items in $NAME$", + "message": "查看 $NAME$ 中的项目", "description": "Button to view the contents of a folder or collection", "placeholders": { "name": { @@ -8197,7 +8263,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "返回 $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -8207,11 +8273,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": { @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "升级到付费计划以加强监控,从而提前发现安全漏洞。" + }, + "approveAllRequests": { + "message": "批准所有请求" + }, + "allLoginRequestsApproved": { + "message": "所有登录请求已批准" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "未验证" + }, + "verified": { + "message": "已验证" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 921acabb8ec..e8413ed6fac 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -581,9 +581,15 @@ "accessLevel": { "message": "存取等級" }, + "accessing": { + "message": "Accessing" + }, "loggedOut": { "message": "已登出" }, + "loggedOutDesc": { + "message": "You have been logged out of your account." + }, "loginExpired": { "message": "您的登入工作階段已逾期。" }, @@ -615,10 +621,10 @@ "message": "使用主密碼登入" }, "readingPasskeyLoading": { - "message": "正在讀取金輪..." + "message": "正在讀取密碼金輪..." }, "readingPasskeyLoadingInfo": { - "message": "請保持視窗開啟,並按照瀏覽器指示操作。" + "message": "保持此視窗打開,然後按照瀏覽器的提示進行操作。" }, "useADifferentLogInMethod": { "message": "使用不同的登入方法" @@ -627,10 +633,10 @@ "message": "使用密碼金鑰登入" }, "invalidPasskeyPleaseTryAgain": { - "message": "無效的金鑰。請再試一次。" + "message": "無效的密碼金鑰,請再試一次。" }, "twoFactorForPasskeysNotSupportedOnClientUpdateToLogIn": { - "message": "不支援金鑰的多重要素驗證。更新應用程式以登入。" + "message": "不支援密碼金鑰 2FA。請更新 app 以登入。" }, "loginWithPasskeyInfo": { "message": "使用已產生的密碼金輪,無需密碼即可自動登入。生物特徵辨識(例如面部識別或指紋)或其他 FIDO2 安全方式將用於確認您的身分。" @@ -642,7 +648,7 @@ "message": "瞭解更多關於無密碼登入的資訊" }, "creatingPasskeyLoading": { - "message": "正在建立密碼金輪..." + "message": "正在建立密碼金鑰..." }, "creatingPasskeyLoadingInfo": { "message": "保持此視窗打開,然後按照瀏覽器的提示進行操作。" @@ -657,7 +663,7 @@ "message": "密碼金鑰已成功建立!" }, "customPasskeyNameInfo": { - "message": "命名您的密碼金輪以方便您辨識它。" + "message": "命名您的密碼金鑰以方便您辨識它。" }, "useForVaultEncryption": { "message": "用於密碼庫加密" @@ -1047,6 +1053,12 @@ "copyUuid": { "message": "複製 UUID" }, + "errorRefreshingAccessToken": { + "message": "Access Token Refresh Error" + }, + "errorRefreshingAccessTokenDesc": { + "message": "No refresh token or API keys found. Please try logging out and logging back in." + }, "warning": { "message": "警告" }, @@ -1991,7 +2003,7 @@ } }, "breachFound": { - "message": "發現已外洩的帳號" + "message": "發現已外洩的帳戶" }, "compromisedData": { "message": "遭外洩的資料" @@ -3244,6 +3256,27 @@ "device": { "message": "裝置" }, + "creatingAccountOn": { + "message": "Creating account on" + }, + "checkYourEmail": { + "message": "Check your email" + }, + "followTheLinkInTheEmailSentTo": { + "message": "Follow the link in the email sent to" + }, + "andContinueCreatingYourAccount": { + "message": "and continue creating your account." + }, + "noEmail": { + "message": "No email?" + }, + "goBack": { + "message": "Go back" + }, + "toEditYourEmailAddress": { + "message": "to edit your email address." + }, "view": { "message": "檢視" }, @@ -5562,6 +5595,39 @@ "rotateBillingSyncTokenTitle": { "message": "輪換「計費同步權杖」會導致之前的權杖失效。" }, + "selfHostedServer": { + "message": "self-hosted" + }, + "customEnvironment": { + "message": "Custom environment" + }, + "selfHostedBaseUrlHint": { + "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." + }, + "selfHostedEnvFormInvalid": { + "message": "You must add either the base Server URL or at least one custom environment." + }, + "apiUrl": { + "message": "API server URL" + }, + "webVaultUrl": { + "message": "Web vault server URL" + }, + "identityUrl": { + "message": "Identity server URL" + }, + "notificationsUrl": { + "message": "Notifications server URL" + }, + "iconsUrl": { + "message": "Icons server URL" + }, + "environmentSaved": { + "message": "Environment URLs saved" + }, "selfHostingTitle": { "message": "自我裝載" }, @@ -8236,7 +8302,7 @@ "message": "更新了組織名稱" }, "providerPlan": { - "message": "已管理的服務提供者" + "message": "代管服務供應商" }, "orgSeats": { "message": "組織席位" @@ -8267,5 +8333,26 @@ }, "upgradeOrganizationCloseSecurityGapsDesc": { "message": "Stay ahead of security vulnerabilities by upgrading to a paid plan for enhanced monitoring." + }, + "approveAllRequests": { + "message": "Approve all requests" + }, + "allLoginRequestsApproved": { + "message": "All login requests approved" + }, + "payPal": { + "message": "PayPal" + }, + "bitcoin": { + "message": "Bitcoin" + }, + "updatedTaxInformation": { + "message": "Updated tax information" + }, + "unverified": { + "message": "Unverified" + }, + "verified": { + "message": "Verified" } } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 07d244ee602..cd3e969e2d5 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -12,6 +12,9 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], + "@bitwarden/generator-extensions": ["../../libs/tools/generator/extensions/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], @@ -19,6 +22,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["src/*"] } diff --git a/apps/web/webpack.config.js b/apps/web/webpack.config.js index 815a8aff9e3..f22d98f081d 100644 --- a/apps/web/webpack.config.js +++ b/apps/web/webpack.config.js @@ -68,8 +68,7 @@ const moduleRules = [ { loader: "babel-loader", options: { - configFile: false, - plugins: ["@angular/compiler-cli/linker/babel"], + configFile: "../../babel.config.json", }, }, ], diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 00000000000..4d817f0abf4 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "bugfixes": true + } + ] + ], + "plugins": ["@angular/compiler-cli/linker/babel"] +} 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 new file mode 100644 index 00000000000..3214a0fc41e --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve-all.command.ts @@ -0,0 +1,52 @@ +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/services/organization/organization.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +export class ApproveAllCommand { + constructor( + private organizationAuthRequestService: OrganizationAuthRequestService, + private organizationService: OrganizationService, + ) {} + + async run(organizationId: string): Promise { + if (organizationId != null) { + organizationId = organizationId.toLowerCase(); + } + + if (!Utils.isGuid(organizationId)) { + return Response.badRequest("`" + organizationId + "` is not a GUID."); + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (!organization?.canManageUsersPassword) { + return Response.error( + "You do not have permission to approve pending device authorization requests.", + ); + } + + try { + const pendingApprovals = + await this.organizationAuthRequestService.listPendingRequests(organizationId); + if (pendingApprovals.length == 0) { + const res = new MessageResponse( + "No pending device authorization requests to approve.", + null, + ); + return Response.success(res); + } + + await this.organizationAuthRequestService.approvePendingRequests( + organizationId, + pendingApprovals, + ); + + return Response.success(); + } catch (e) { + return Response.error(e); + } + } +} 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 new file mode 100644 index 00000000000..8efa172296c --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/approve.command.ts @@ -0,0 +1,54 @@ +import { firstValueFrom } from "rxjs"; + +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"; + +export class ApproveCommand { + constructor( + private organizationService: OrganizationService, + private organizationAuthRequestService: OrganizationAuthRequestService, + ) {} + + async run(organizationId: string, id: string): Promise { + if (organizationId != null) { + organizationId = organizationId.toLowerCase(); + } + + if (!Utils.isGuid(organizationId)) { + return Response.badRequest("`" + organizationId + "` is not a GUID."); + } + + if (id != null) { + id = id.toLowerCase(); + } + + if (!Utils.isGuid(id)) { + return Response.badRequest("`" + id + "` is not a GUID."); + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (!organization?.canManageUsersPassword) { + return Response.error( + "You do not have permission to approve pending device authorization requests.", + ); + } + + try { + const pendingRequests = + await this.organizationAuthRequestService.listPendingRequests(organizationId); + + const request = pendingRequests.find((r) => r.id == id); + if (request == null) { + return Response.error("Invalid request id"); + } + + await this.organizationAuthRequestService.approvePendingRequest(organizationId, request); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } +} 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 new file mode 100644 index 00000000000..59cc4235ebf --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny-all.command.ts @@ -0,0 +1,49 @@ +import { firstValueFrom } from "rxjs"; + +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"; + +export class DenyAllCommand { + constructor( + private organizationService: OrganizationService, + private organizationAuthRequestService: OrganizationAuthRequestService, + ) {} + + async run(organizationId: string): Promise { + if (organizationId != null) { + organizationId = organizationId.toLowerCase(); + } + + if (!Utils.isGuid(organizationId)) { + return Response.badRequest("`" + organizationId + "` is not a GUID."); + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (!organization?.canManageUsersPassword) { + return Response.error( + "You do not have permission to approve pending device authorization requests.", + ); + } + + try { + const pendingRequests = + await this.organizationAuthRequestService.listPendingRequests(organizationId); + if (pendingRequests.length == 0) { + const res = new MessageResponse("No pending device authorization requests to deny.", null); + return Response.success(res); + } + + await this.organizationAuthRequestService.denyPendingRequests( + organizationId, + ...pendingRequests.map((r) => r.id), + ); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } +} 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 new file mode 100644 index 00000000000..a9676d3fc54 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/deny.command.ts @@ -0,0 +1,46 @@ +import { firstValueFrom } from "rxjs"; + +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"; + +export class DenyCommand { + constructor( + private organizationService: OrganizationService, + private organizationAuthRequestService: OrganizationAuthRequestService, + ) {} + + async run(organizationId: string, id: string): Promise { + if (organizationId != null) { + organizationId = organizationId.toLowerCase(); + } + + if (!Utils.isGuid(organizationId)) { + return Response.badRequest("`" + organizationId + "` is not a GUID."); + } + + if (id != null) { + id = id.toLowerCase(); + } + + if (!Utils.isGuid(id)) { + return Response.badRequest("`" + id + "` is not a GUID."); + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (!organization?.canManageUsersPassword) { + return Response.error( + "You do not have permission to approve pending device authorization requests.", + ); + } + + try { + await this.organizationAuthRequestService.denyPendingRequests(organizationId, id); + return Response.success(); + } catch (e) { + return Response.error(e); + } + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts new file mode 100644 index 00000000000..0b0f3bb0f91 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/device-approval.program.ts @@ -0,0 +1,119 @@ +import { program, Command } from "commander"; + +import { BaseProgram } from "@bitwarden/cli/base-program"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + +import { ServiceContainer } from "../../service-container"; + +import { ApproveAllCommand } from "./approve-all.command"; +import { ApproveCommand } from "./approve.command"; +import { DenyAllCommand } from "./deny-all.command"; +import { DenyCommand } from "./deny.command"; +import { ListCommand } from "./list.command"; + +export class DeviceApprovalProgram extends BaseProgram { + constructor(protected serviceContainer: ServiceContainer) { + super(serviceContainer); + } + + register() { + program.addCommand(this.deviceApprovalCommand()); + } + + private deviceApprovalCommand() { + return new Command("device-approval") + .description("Manage device approvals") + .addCommand(this.listCommand()) + .addCommand(this.approveCommand()) + .addCommand(this.approveAllCommand()) + .addCommand(this.denyCommand()) + .addCommand(this.denyAllCommand()); + } + + private listCommand(): Command { + return new Command("list") + .description("List all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new ListCommand( + this.serviceContainer.organizationAuthRequestService, + this.serviceContainer.organizationService, + ); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } + + private approveCommand(): Command { + return new Command("approve") + .argument("", "The id of the organization") + .argument("", "The id of the request to approve") + .description("Approve a pending request") + .action(async (organizationId: string, id: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new ApproveCommand( + this.serviceContainer.organizationService, + this.serviceContainer.organizationAuthRequestService, + ); + const response = await cmd.run(organizationId, id); + this.processResponse(response); + }); + } + + private approveAllCommand(): Command { + return new Command("approve-all") + .description("Approve all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new ApproveAllCommand( + this.serviceContainer.organizationAuthRequestService, + this.serviceContainer.organizationService, + ); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } + + private denyCommand(): Command { + return new Command("deny") + .argument("", "The id of the organization") + .argument("", "The id of the request to deny") + .description("Deny a pending request") + .action(async (organizationId: string, id: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new DenyCommand( + this.serviceContainer.organizationService, + this.serviceContainer.organizationAuthRequestService, + ); + const response = await cmd.run(organizationId, id); + this.processResponse(response); + }); + } + + private denyAllCommand(): Command { + return new Command("deny-all") + .description("Deny all pending requests for an organization") + .argument("") + .action(async (organizationId: string) => { + await this.exitIfFeatureFlagDisabled(FeatureFlag.BulkDeviceApproval); + await this.exitIfLocked(); + + const cmd = new DenyAllCommand( + this.serviceContainer.organizationService, + this.serviceContainer.organizationAuthRequestService, + ); + const response = await cmd.run(organizationId); + this.processResponse(response); + }); + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts new file mode 100644 index 00000000000..399f89623ec --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/index.ts @@ -0,0 +1 @@ +export { DeviceApprovalProgram } from "./device-approval.program"; diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts new file mode 100644 index 00000000000..10da11b35cb --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/list.command.ts @@ -0,0 +1,42 @@ +import { firstValueFrom } from "rxjs"; + +import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests"; +import { Response } from "@bitwarden/cli/models/response"; +import { ListResponse } from "@bitwarden/cli/models/response/list.response"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { PendingAuthRequestResponse } from "./pending-auth-request.response"; + +export class ListCommand { + constructor( + private organizationAuthRequestService: OrganizationAuthRequestService, + private organizationService: OrganizationService, + ) {} + + async run(organizationId: string): Promise { + if (organizationId != null) { + organizationId = organizationId.toLowerCase(); + } + + if (!Utils.isGuid(organizationId)) { + return Response.badRequest("`" + organizationId + "` is not a GUID."); + } + + const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + if (!organization?.canManageUsersPassword) { + return Response.error( + "You do not have permission to approve pending device authorization requests.", + ); + } + + try { + const requests = + await this.organizationAuthRequestService.listPendingRequests(organizationId); + const res = new ListResponse(requests.map((r) => new PendingAuthRequestResponse(r))); + return Response.success(res); + } catch (e) { + return Response.error(e); + } + } +} diff --git a/bitwarden_license/bit-cli/src/admin-console/device-approval/pending-auth-request.response.ts b/bitwarden_license/bit-cli/src/admin-console/device-approval/pending-auth-request.response.ts new file mode 100644 index 00000000000..991b3fb8e58 --- /dev/null +++ b/bitwarden_license/bit-cli/src/admin-console/device-approval/pending-auth-request.response.ts @@ -0,0 +1,26 @@ +import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/"; +import { BaseResponse } from "@bitwarden/cli/models/response/base.response"; + +export class PendingAuthRequestResponse implements BaseResponse { + object = "auth-request"; + + id: string; + userId: string; + organizationUserId: string; + email: string; + requestDeviceIdentifier: string; + requestDeviceType: string; + requestIpAddress: string; + creationDate: Date; + + constructor(authRequest: PendingAuthRequestView) { + this.id = authRequest.id; + this.userId = authRequest.userId; + this.organizationUserId = authRequest.organizationUserId; + this.email = authRequest.email; + this.requestDeviceIdentifier = authRequest.requestDeviceIdentifier; + this.requestDeviceType = authRequest.requestDeviceType; + this.requestIpAddress = authRequest.requestIpAddress; + this.creationDate = authRequest.creationDate; + } +} diff --git a/bitwarden_license/bit-cli/src/register-bit-programs.ts b/bitwarden_license/bit-cli/src/register-bit-programs.ts index 859574644ad..0d3f5e39f25 100644 --- a/bitwarden_license/bit-cli/src/register-bit-programs.ts +++ b/bitwarden_license/bit-cli/src/register-bit-programs.ts @@ -1,3 +1,4 @@ +import { DeviceApprovalProgram } from "./admin-console/device-approval"; import { ServiceContainer } from "./service-container"; /** @@ -7,4 +8,6 @@ import { ServiceContainer } from "./service-container"; * myProgram.register(); * @param serviceContainer A class that instantiates services and makes them available for dependency injection */ -export async function registerBitPrograms(serviceContainer: ServiceContainer) {} +export async function registerBitPrograms(serviceContainer: ServiceContainer) { + new DeviceApprovalProgram(serviceContainer).register(); +} diff --git a/bitwarden_license/bit-cli/src/service-container.ts b/bitwarden_license/bit-cli/src/service-container.ts index 369d54113d6..995e14531d7 100644 --- a/bitwarden_license/bit-cli/src/service-container.ts +++ b/bitwarden_license/bit-cli/src/service-container.ts @@ -1,7 +1,24 @@ +import { + OrganizationAuthRequestService, + OrganizationAuthRequestApiService, +} from "@bitwarden/bit-common/admin-console/auth-requests"; import { ServiceContainer as OssServiceContainer } from "@bitwarden/cli/service-container"; /** * Instantiates services and makes them available for dependency injection. * Any Bitwarden-licensed services should be registered here. */ -export class ServiceContainer extends OssServiceContainer {} +export class ServiceContainer extends OssServiceContainer { + organizationAuthRequestApiService: OrganizationAuthRequestApiService; + organizationAuthRequestService: OrganizationAuthRequestService; + + constructor() { + super(); + this.organizationAuthRequestApiService = new OrganizationAuthRequestApiService(this.apiService); + this.organizationAuthRequestService = new OrganizationAuthRequestService( + this.organizationAuthRequestApiService, + this.cryptoService, + this.organizationUserService, + ); + } +} diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index 1989aa08f9b..e8a57e5eb04 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -21,7 +21,8 @@ "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], - "@bitwarden/node/*": ["../../libs/node/src/*"] + "@bitwarden/node/*": ["../../libs/node/src/*"], + "@bitwarden/bit-common/*": ["../../bitwarden_license/bit-common/src/*"] } }, "include": ["src", "src/**/*.spec.ts"] diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts index d8c4bacd697..517dc8699b5 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/index.ts @@ -1,2 +1,4 @@ export * from "./pending-organization-auth-request.response"; export * from "./organization-auth-request.service"; +export * from "./organization-auth-request-api.service"; +export * from "./pending-auth-request.view"; diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 6b40d447419..1c81cd9a1ee 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -11,10 +11,14 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], + "@bitwarden/generator-extensions": ["../../libs/tools/generator/extensions/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], "@bitwarden/vault-export-ui": ["../../libs/tools/export/vault-export/vault-export-core/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@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.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html index 5edc1f58674..cfc6538dbd9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.html @@ -28,6 +28,16 @@ appA11yTitle="{{ 'options' | i18n }}" > +
      - + {{ "seats" | i18n }} + + {{ unassignedSeatsForSelectedPlan }} + {{ "unassignedSeatsDescription" | i18n | lowercase }} + 0 {{ "purchaseSeatDescription" | i18n | lowercase }} +
      diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts index 8427572516a..13d74136cf4 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-organization.component.ts @@ -2,11 +2,12 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; import { PlanType } from "@bitwarden/common/billing/enums"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { ProviderPlanResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { DialogService, ToastService } from "@bitwarden/components"; import { WebProviderService } from "../../../admin-console/providers/services/web-provider.service"; @@ -33,6 +34,7 @@ type PlanCard = { name: string; cost: number; type: PlanType; + plan: PlanResponse; selected: boolean; }; @@ -41,20 +43,24 @@ type PlanCard = { templateUrl: "./create-client-organization.component.html", }) export class CreateClientOrganizationComponent implements OnInit { - protected ResultType = CreateClientOrganizationResultType; protected formGroup = this.formBuilder.group({ clientOwnerEmail: ["", [Validators.required, Validators.email]], organizationName: ["", Validators.required], seats: [null, [Validators.required, Validators.min(1)]], }); + protected loading = true; protected planCards: PlanCard[]; + protected ResultType = CreateClientOrganizationResultType; + + private providerPlans: ProviderPlanResponse[]; constructor( + private billingApiService: BillingApiServiceAbstraction, @Inject(DIALOG_DATA) private dialogParams: CreateClientOrganizationParams, private dialogRef: DialogRef, private formBuilder: FormBuilder, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, + private toastService: ToastService, private webProviderService: WebProviderService, ) {} @@ -92,6 +98,11 @@ export class CreateClientOrganizationComponent implements OnInit { } async ngOnInit(): Promise { + const subscription = await this.billingApiService.getProviderSubscription( + this.dialogParams.providerId, + ); + this.providerPlans = subscription?.plans ?? []; + const teamsPlan = this.dialogParams.plans.find((plan) => plan.type === PlanType.TeamsMonthly); const enterprisePlan = this.dialogParams.plans.find( (plan) => plan.type === PlanType.EnterpriseMonthly, @@ -102,15 +113,19 @@ export class CreateClientOrganizationComponent implements OnInit { name: this.i18nService.t("planNameTeams"), cost: teamsPlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, type: teamsPlan.type, + plan: teamsPlan, selected: true, }, { name: this.i18nService.t("planNameEnterprise"), cost: enterprisePlan.PasswordManager.seatPrice * 0.65, // 35% off for MSPs, type: enterprisePlan.type, + plan: enterprisePlan, selected: false, }, ]; + + this.loading = false; } protected selectPlan(name: string) { @@ -135,8 +150,23 @@ export class CreateClientOrganizationComponent implements OnInit { this.formGroup.value.seats, ); - this.platformUtilsService.showToast("success", null, this.i18nService.t("createdNewClient")); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("createdNewClient"), + }); this.dialogRef.close(this.ResultType.Submitted); }; + + protected get unassignedSeatsForSelectedPlan(): number { + if (this.loading || !this.planCards) { + return 0; + } + const selectedPlan = this.planCards.find((planCard) => planCard.selected).plan; + const selectedProviderPlan = this.providerPlans.find( + (providerPlan) => providerPlan.planName === selectedPlan.name, + ); + return selectedProviderPlan.seatMinimum - selectedProviderPlan.assignedSeats; + } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html index d1e4fe8b1f3..8181c285c28 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-organization-subscription.component.html @@ -7,22 +7,20 @@

      {{ "manageSeatsDescription" | i18n }}

      - + {{ "assignedSeats" | i18n }} + +
      + {{ unassignedSeats }} {{ "unassignedSeatsDescription" | i18n | lowercase }} + 0 {{ "purchaseSeatDescription" | i18n | lowercase }} +
      +
      - -

      - {{ unassignedSeats }} - {{ "unassignedSeatsDescription" | i18n }} -

      -

      - {{ AdditionalSeatPurchased }} - {{ "purchaseSeatDescription" | i18n }} -

      -
      + + + +

      {{ "paymentMethod" | i18n }}

      +

      {{ "noPaymentMethod" | i18n }}

      + + +

      + + {{ paymentMethodDescription }} +

      +
      + +
      + + +

      {{ "taxInformation" | i18n }}

      +

      {{ "taxInformationDesc" | i18n }}

      + +
      + diff --git a/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-payment-method.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-payment-method.component.ts new file mode 100644 index 00000000000..42a7dbdec05 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-payment-method.component.ts @@ -0,0 +1,140 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute } from "@angular/router"; +import { from, lastValueFrom, Subject, switchMap } from "rxjs"; +import { takeUntil } from "rxjs/operators"; + +import { openAddAccountCreditDialog } from "@bitwarden/angular/billing/components"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { MaskedPaymentMethod, TaxInformation } from "@bitwarden/common/billing/models/domain"; +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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +import { + openProviderSelectPaymentMethodDialog, + ProviderSelectPaymentMethodDialogResultType, +} from "./provider-select-payment-method-dialog.component"; + +@Component({ + selector: "app-provider-payment-method", + templateUrl: "./provider-payment-method.component.html", +}) +export class ProviderPaymentMethodComponent implements OnInit, OnDestroy { + protected providerId: string; + protected loading: boolean; + + protected accountCredit: number; + protected maskedPaymentMethod: MaskedPaymentMethod; + protected taxInformation: TaxInformation; + + private destroy$ = new Subject(); + + constructor( + private activatedRoute: ActivatedRoute, + private billingApiService: BillingApiServiceAbstraction, + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + addAccountCredit = () => + openAddAccountCreditDialog(this.dialogService, { + data: { + providerId: this.providerId, + }, + }); + + changePaymentMethod = async () => { + const dialogRef = openProviderSelectPaymentMethodDialog(this.dialogService, { + data: { + providerId: this.providerId, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result == ProviderSelectPaymentMethodDialogResultType.Submitted) { + await this.load(); + } + }; + + async load() { + this.loading = true; + const paymentInformation = await this.billingApiService.getProviderPaymentInformation( + this.providerId, + ); + this.accountCredit = paymentInformation.accountCredit; + this.maskedPaymentMethod = MaskedPaymentMethod.from(paymentInformation.paymentMethod); + this.taxInformation = TaxInformation.from(paymentInformation.taxInformation); + this.loading = false; + } + + onDataUpdated = async () => await this.load(); + + updateTaxInformation = async (taxInformation: TaxInformation) => { + const request = ExpandedTaxInfoUpdateRequest.From(taxInformation); + await this.billingApiService.updateProviderTaxInformation(this.providerId, request); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("updatedTaxInformation"), + }); + }; + + verifyBankAccount = async (amount1: number, amount2: number) => { + const request = new VerifyBankAccountRequest(amount1, amount2); + await this.billingApiService.verifyProviderBankAccount(this.providerId, request); + }; + + ngOnInit() { + this.activatedRoute.params + .pipe( + switchMap(({ providerId }) => { + this.providerId = providerId; + return from(this.load()); + }), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + protected get hasPaymentMethod(): boolean { + return !!this.maskedPaymentMethod; + } + + protected get hasUnverifiedPaymentMethod(): boolean { + return !!this.maskedPaymentMethod && this.maskedPaymentMethod.needsVerification; + } + + protected get paymentMethodClass(): string[] { + switch (this.maskedPaymentMethod.type) { + case PaymentMethodType.Card: + return ["bwi-credit-card"]; + case PaymentMethodType.BankAccount: + return ["bwi-bank"]; + case PaymentMethodType.PayPal: + return ["bwi-paypal tw-text-primary"]; + default: + return []; + } + } + + protected get paymentMethodDescription(): string { + let description = this.maskedPaymentMethod.description; + if (this.maskedPaymentMethod.type === PaymentMethodType.BankAccount) { + if (this.hasUnverifiedPaymentMethod) { + description += " - " + this.i18nService.t("unverified"); + } else { + description += " - " + this.i18nService.t("verified"); + } + } + return description; + } +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.html b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.html new file mode 100644 index 00000000000..03e8405a48c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.html @@ -0,0 +1,18 @@ +
      + + + {{ "addPaymentMethod" | i18n }} + + + + + + + + + +
      diff --git a/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.ts new file mode 100644 index 00000000000..09a293d12d8 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/payment-method/provider-select-payment-method-dialog.component.ts @@ -0,0 +1,60 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, EventEmitter, Inject, Output, ViewChild } from "@angular/core"; +import { FormGroup } from "@angular/forms"; + +import { SelectPaymentMethodComponent } from "@bitwarden/angular/billing/components"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { TokenizedPaymentMethodRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-method.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +type ProviderSelectPaymentMethodDialogParams = { + providerId: string; +}; + +export enum ProviderSelectPaymentMethodDialogResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openProviderSelectPaymentMethodDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open< + ProviderSelectPaymentMethodDialogResultType, + ProviderSelectPaymentMethodDialogParams + >(ProviderSelectPaymentMethodDialogComponent, dialogConfig); + +@Component({ + templateUrl: "provider-select-payment-method-dialog.component.html", +}) +export class ProviderSelectPaymentMethodDialogComponent { + @ViewChild(SelectPaymentMethodComponent) + selectPaymentMethodComponent: SelectPaymentMethodComponent; + @Output() providerPaymentMethodUpdated = new EventEmitter(); + + protected readonly formGroup = new FormGroup({}); + protected readonly ResultType = ProviderSelectPaymentMethodDialogResultType; + + constructor( + private billingApiService: BillingApiServiceAbstraction, + @Inject(DIALOG_DATA) private dialogParams: ProviderSelectPaymentMethodDialogParams, + private dialogRef: DialogRef, + private i18nService: I18nService, + private toastService: ToastService, + ) {} + + submit = async () => { + const tokenizedPaymentMethod = await this.selectPaymentMethodComponent.tokenizePaymentMethod(); + const request = TokenizedPaymentMethodRequest.From(tokenizedPaymentMethod); + await this.billingApiService.updateProviderPaymentMethod(this.dialogParams.providerId, request); + this.providerPaymentMethodUpdated.emit(); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("updatedPaymentMethod"), + }); + this.dialogRef.close(this.ResultType.Submitted); + }; +} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html similarity index 73% rename from bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html rename to bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html index fdcb8a67018..47f8aa375c6 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/provider-subscription.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.html @@ -1,32 +1,10 @@ - {{ "loading" | i18n }} - - - - {{ "subscriptionCanceled" | i18n }} - -
      -
      {{ "billingPlan" | i18n }}
      -
      {{ "providerPlan" | i18n }}
      - -
      {{ "status" | i18n }}
      -
      - {{ subscription.status }} -
      -
      {{ "nextCharge" | i18n }}
      -
      - {{ subscription.currentPeriodEndDate | date: "mediumDate" }} -
      -
      -
      -
      - +
      1 ? totalSeats.toString() : ""; } - sumCost(plans: Plans[]): number { + sumCost(plans: ProviderPlanResponse[]): number { return plans.reduce((acc, plan) => acc + plan.cost, 0); } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.html new file mode 100644 index 00000000000..0c80d3ef693 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.html @@ -0,0 +1,32 @@ + + +

      {{ data.callout.body }}

      + +
      +
      +
      {{ "billingPlan" | i18n }}
      +
      {{ "providerPlan" | i18n }}
      + +
      {{ data.status.label }}
      +
      + + {{ displayedStatus }} + +
      +
      + {{ data.date.label | titlecase }} +
      +
      + {{ data.date.value | date: "mediumDate" }} +
      +
      +
      +
      diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.ts new file mode 100644 index 00000000000..fa9a892254e --- /dev/null +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/subscription-status.component.ts @@ -0,0 +1,188 @@ +import { DatePipe } from "@angular/common"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; + +import { ProviderSubscriptionResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +type ComponentData = { + status?: { + label: string; + value: string; + }; + date?: { + label: string; + value: string; + }; + callout?: { + severity: "danger" | "warning"; + header: string; + body: string; + showReinstatementButton: boolean; + }; +}; + +@Component({ + selector: "app-subscription-status", + templateUrl: "subscription-status.component.html", +}) +export class SubscriptionStatusComponent { + @Input({ required: true }) providerSubscriptionResponse: ProviderSubscriptionResponse; + @Output() reinstatementRequested = new EventEmitter(); + + constructor( + private datePipe: DatePipe, + private i18nService: I18nService, + ) {} + + get displayedStatus(): string { + return this.data.status.value; + } + + get planName() { + return this.providerSubscriptionResponse.plans[0]; + } + + get status(): string { + return this.subscription.status; + } + + get isExpired() { + return this.subscription.status !== "active"; + } + + get subscription() { + return this.providerSubscriptionResponse; + } + + get data(): ComponentData { + const defaultStatusLabel = this.i18nService.t("status"); + + const nextChargeDateLabel = this.i18nService.t("nextCharge"); + const subscriptionExpiredDateLabel = this.i18nService.t("subscriptionExpired"); + const cancellationDateLabel = this.i18nService.t("cancellationDate"); + + switch (this.status) { + case "free": { + return {}; + } + case "trialing": { + return { + status: { + label: defaultStatusLabel, + value: this.i18nService.t("trial"), + }, + date: { + label: nextChargeDateLabel, + value: this.subscription.currentPeriodEndDate.toDateString(), + }, + }; + } + case "active": { + return { + status: { + label: defaultStatusLabel, + value: this.i18nService.t("active"), + }, + date: { + label: nextChargeDateLabel, + value: this.subscription.currentPeriodEndDate.toDateString(), + }, + }; + } + case "past_due": { + const pastDueText = this.i18nService.t("pastDue"); + const suspensionDate = this.datePipe.transform( + this.subscription.suspensionDate, + "mediumDate", + ); + const calloutBody = + this.subscription.collectionMethod === "charge_automatically" + ? this.i18nService.t( + "pastDueWarningForChargeAutomatically", + this.subscription.gracePeriod, + suspensionDate, + ) + : this.i18nService.t( + "pastDueWarningForSendInvoice", + this.subscription.gracePeriod, + suspensionDate, + ); + return { + status: { + label: defaultStatusLabel, + value: pastDueText, + }, + date: { + label: subscriptionExpiredDateLabel, + value: this.subscription.unpaidPeriodEndDate, + }, + callout: { + severity: "warning", + header: pastDueText, + body: calloutBody, + showReinstatementButton: false, + }, + }; + } + case "unpaid": { + return { + status: { + label: defaultStatusLabel, + value: this.i18nService.t("unpaid"), + }, + date: { + label: subscriptionExpiredDateLabel, + value: this.subscription.currentPeriodEndDate.toDateString(), + }, + callout: { + severity: "danger", + header: this.i18nService.t("unpaidInvoice"), + body: this.i18nService.t("toReactivateYourSubscription"), + showReinstatementButton: false, + }, + }; + } + case "pending_cancellation": { + const pendingCancellationText = this.i18nService.t("pendingCancellation"); + return { + status: { + label: defaultStatusLabel, + value: pendingCancellationText, + }, + date: { + label: cancellationDateLabel, + value: this.subscription.currentPeriodEndDate.toDateString(), + }, + callout: { + severity: "warning", + header: pendingCancellationText, + body: this.i18nService.t("subscriptionPendingCanceled"), + showReinstatementButton: true, + }, + }; + } + case "incomplete_expired": + case "canceled": { + const canceledText = this.i18nService.t("canceled"); + return { + status: { + label: defaultStatusLabel, + value: canceledText, + }, + date: { + label: cancellationDateLabel, + value: this.subscription.currentPeriodEndDate.toDateString(), + }, + callout: { + severity: "danger", + header: canceledText, + body: this.i18nService.t("subscriptionCanceled"), + showReinstatementButton: false, + }, + }; + } + } + } + + requestReinstatement = () => this.reinstatementRequested.emit(); +} 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 76be88b610e..0863bb0cccf 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,44 +1,26 @@ -export class BaseAccessPolicyView { - id: string; +class BaseAccessPolicyView { read: boolean; write: boolean; - creationDate: string; - revisionDate: string; } -export class UserProjectAccessPolicyView extends BaseAccessPolicyView { +export class UserAccessPolicyView extends BaseAccessPolicyView { organizationUserId: string; organizationUserName: string; - grantedProjectId: string; - userId: string; currentUser: boolean; } -export class UserServiceAccountAccessPolicyView extends BaseAccessPolicyView { - organizationUserId: string; - organizationUserName: string; - grantedServiceAccountId: string; - userId: string; - currentUser: boolean; -} - -export class GroupProjectAccessPolicyView extends BaseAccessPolicyView { +export class GroupAccessPolicyView extends BaseAccessPolicyView { groupId: string; groupName: string; - grantedProjectId: string; currentUserInGroup: boolean; } -export class GroupServiceAccountAccessPolicyView extends BaseAccessPolicyView { - groupId: string; - groupName: string; - grantedServiceAccountId: string; - currentUserInGroup: boolean; -} - -export class ServiceAccountProjectAccessPolicyView extends BaseAccessPolicyView { +export class ServiceAccountAccessPolicyView extends BaseAccessPolicyView { serviceAccountId: string; serviceAccountName: string; +} + +export class GrantedProjectAccessPolicyView extends BaseAccessPolicyView { grantedProjectId: string; grantedProjectName: 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 c85b289928b..9a35e76a615 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,6 +1,6 @@ -import { GroupProjectAccessPolicyView, UserProjectAccessPolicyView } from "./access-policy.view"; +import { GroupAccessPolicyView, UserAccessPolicyView } from "./access-policy.view"; export class ProjectPeopleAccessPoliciesView { - userAccessPolicies: UserProjectAccessPolicyView[]; - groupAccessPolicies: GroupProjectAccessPolicyView[]; + userAccessPolicies: UserAccessPolicyView[]; + groupAccessPolicies: GroupAccessPolicyView[]; } 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 28636c4df1d..9faaa29b697 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,5 +1,5 @@ -import { ServiceAccountProjectAccessPolicyView } from "./access-policy.view"; +import { ServiceAccountAccessPolicyView } from "./access-policy.view"; export class ProjectServiceAccountsAccessPoliciesView { - serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyView[]; + serviceAccountAccessPolicies: ServiceAccountAccessPolicyView[]; } 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 new file mode 100644 index 00000000000..8742021a421 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/models/view/access-policies/secret-access-policies.view.ts @@ -0,0 +1,11 @@ +import { + GroupAccessPolicyView, + UserAccessPolicyView, + ServiceAccountAccessPolicyView, +} from "./access-policy.view"; + +export class SecretAccessPoliciesView { + userAccessPolicies: UserAccessPolicyView[]; + groupAccessPolicies: GroupAccessPolicyView[]; + serviceAccountAccessPolicies: ServiceAccountAccessPolicyView[]; +} 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 7d53e38263a..e055daa199a 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,10 +1,10 @@ -import { ServiceAccountProjectAccessPolicyView } from "./access-policy.view"; +import { GrantedProjectAccessPolicyView } from "./access-policy.view"; export class ServiceAccountGrantedPoliciesView { - grantedProjectPolicies: ServiceAccountProjectPolicyPermissionDetailsView[]; + grantedProjectPolicies: GrantedProjectPolicyPermissionDetailsView[]; } -export class ServiceAccountProjectPolicyPermissionDetailsView { - accessPolicy: ServiceAccountProjectAccessPolicyView; +export class GrantedProjectPolicyPermissionDetailsView { + accessPolicy: GrantedProjectAccessPolicyView; hasPermission: boolean; } 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 58dcf6d4706..2ef4eedc33e 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,9 +1,6 @@ -import { - GroupServiceAccountAccessPolicyView, - UserServiceAccountAccessPolicyView, -} from "./access-policy.view"; +import { GroupAccessPolicyView, UserAccessPolicyView } from "./access-policy.view"; export class ServiceAccountPeopleAccessPoliciesView { - userAccessPolicies: UserServiceAccountAccessPolicyView[]; - groupAccessPolicies: GroupServiceAccountAccessPolicyView[]; + userAccessPolicies: UserAccessPolicyView[]; + groupAccessPolicies: GroupAccessPolicyView[]; } 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 255877e4e8d..29cbf78464b 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 @@ -65,6 +65,7 @@ (deleteSecretsEvent)="openDeleteSecret($event)" (newSecretEvent)="openNewSecretDialog()" (editSecretEvent)="openEditSecret($event)" + (viewSecretEvent)="openViewSecret($event)" (copySecretNameEvent)="copySecretName($event)" (copySecretValueEvent)="copySecretValue($event)" (copySecretUuidEvent)="copySecretUuid($event)" 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 95c17642538..4c057e56988 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 @@ -17,6 +17,7 @@ import { import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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"; @@ -40,6 +41,10 @@ import { SecretDialogComponent, SecretOperation, } from "../secrets/dialog/secret-dialog.component"; +import { + SecretViewDialogComponent, + SecretViewDialogParams, +} from "../secrets/dialog/secret-view-dialog.component"; import { SecretService } from "../secrets/secret.service"; import { ServiceAccountDialogComponent, @@ -94,6 +99,7 @@ export class OverviewComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private smOnboardingTasksService: SMOnboardingTasksService, + private logService: LogService, ) {} ngOnInit() { @@ -275,6 +281,15 @@ export class OverviewComponent implements OnInit, OnDestroy { }); } + openViewSecret(secretId: string) { + this.dialogService.open(SecretViewDialogComponent, { + data: { + organizationId: this.organizationId, + secretId: secretId, + }, + }); + } + openDeleteSecret(event: SecretListView[]) { this.dialogService.open(SecretDeleteDialogComponent, { data: { @@ -297,12 +312,13 @@ export class OverviewComponent implements OnInit, OnDestroy { SecretsListComponent.copySecretName(name, this.platformUtilsService, this.i18nService); } - copySecretValue(id: string) { - SecretsListComponent.copySecretValue( + async copySecretValue(id: string) { + await SecretsListComponent.copySecretValue( id, this.platformUtilsService, this.i18nService, this.secretService, + this.logService, ); } @@ -310,11 +326,9 @@ export class OverviewComponent implements OnInit, OnDestroy { SecretsListComponent.copySecretUuid(id, this.platformUtilsService, this.i18nService); } - protected hideOnboarding() { + protected async hideOnboarding() { this.showOnboarding = 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.saveCompletedTasks(this.organizationId, { + await this.saveCompletedTasks(this.organizationId, { importSecrets: true, createSecret: true, createProject: true, 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 0b65bd0a26b..d30d5f664e2 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 @@ -82,9 +82,7 @@ export class ProjectDialogComponent implements OnInit { const projectView = this.getProjectView(); if (this.data.operation === OperationType.Add) { const newProject = await this.createProject(projectView); - // 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(["sm", this.data.organizationId, "projects", newProject.id]); + await this.router.navigate(["sm", this.data.organizationId, "projects", newProject.id]); } else { projectView.id = this.data.projectId; await this.updateProject(projectView); 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 new file mode 100644 index 00000000000..84bc1483fd1 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -0,0 +1,120 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { MockProxy, mock } from "jest-mock-extended"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +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 { RouterService } from "../../../../../../../apps/web/src/app/core/router.service"; +import { ProjectView } from "../../models/view/project.view"; +import { ProjectService } from "../project.service"; + +import { projectAccessGuard } from "./project-access.guard"; + +@Component({ + template: "", +}) +export class GuardedRouteTestComponent {} + +@Component({ + template: "", +}) +export class RedirectTestComponent {} + +describe("Project Redirect Guard", () => { + let organizationService: MockProxy; + let routerService: MockProxy; + let projectServiceMock: MockProxy; + let i18nServiceMock: MockProxy; + let platformUtilsService: MockProxy; + let router: Router; + + const smOrg1 = { id: "123", canAccessSecretsManager: true } as Organization; + const projectView = { + id: "123", + organizationId: "123", + name: "project-name", + creationDate: Date.now.toString(), + revisionDate: Date.now.toString(), + read: true, + write: true, + } as ProjectView; + + beforeEach(async () => { + organizationService = mock(); + routerService = mock(); + projectServiceMock = mock(); + i18nServiceMock = mock(); + platformUtilsService = mock(); + + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { + path: "sm/:organizationId/projects/:projectId", + component: GuardedRouteTestComponent, + canActivate: [projectAccessGuard], + }, + { + path: "sm", + component: RedirectTestComponent, + }, + { + path: "sm/:organizationId/projects", + component: RedirectTestComponent, + }, + ]), + ], + providers: [ + { provide: OrganizationService, useValue: organizationService }, + { provide: RouterService, useValue: routerService }, + { provide: ProjectService, useValue: projectServiceMock }, + { provide: I18nService, useValue: i18nServiceMock }, + { provide: PlatformUtilsService, useValue: platformUtilsService }, + ], + }); + + router = TestBed.inject(Router); + }); + + it("redirects to sm/{orgId}/projects/{projectId} if project exists", async () => { + // Arrange + organizationService.getAll.mockResolvedValue([smOrg1]); + projectServiceMock.getByProjectId.mockReturnValue(Promise.resolve(projectView)); + + // Act + await router.navigateByUrl("sm/123/projects/123"); + + // Assert + expect(router.url).toBe("/sm/123/projects/123"); + }); + + it("redirects to sm/projects if project does not exist", async () => { + // Arrange + organizationService.getAll.mockResolvedValue([smOrg1]); + + // Act + await router.navigateByUrl("sm/123/projects/124"); + + // Assert + expect(router.url).toBe("/sm/123/projects"); + }); + + it("redirects to sm/123/projects if exception occurs while looking for Project", async () => { + // Arrange + jest.spyOn(projectServiceMock, "getByProjectId").mockImplementation(() => { + throw new Error("Test error"); + }); + jest.spyOn(i18nServiceMock, "t").mockReturnValue("Project not found"); + + // Act + await router.navigateByUrl("sm/123/projects/123"); + // Assert + expect(platformUtilsService.showToast).toHaveBeenCalledWith("error", null, "Project not found"); + expect(router.url).toBe("/sm/123/projects"); + }); +}); 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 new file mode 100644 index 00000000000..6c08fcc3aa7 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.ts @@ -0,0 +1,31 @@ +import { inject } from "@angular/core"; +import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { ProjectService } from "../project.service"; + +/** + * Redirects to projects list if the user doesn't have access to project. + */ +export const projectAccessGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { + const projectService = inject(ProjectService); + const platformUtilsService = inject(PlatformUtilsService); + const i18nService = inject(I18nService); + + try { + const project = await projectService.getByProjectId(route.params.projectId); + if (project) { + return true; + } + } catch { + platformUtilsService.showToast( + "error", + null, + i18nService.t("notFound", i18nService.t("project")), + ); + return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "projects"]); + } + return createUrlTreeFromSnapshot(route, ["/sm", route.params.organizationId, "projects"]); +}; 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 835d3825a05..0b0e13fe1f5 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 @@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { combineLatest, Subject, switchMap, takeUntil, catchError } from "rxjs"; 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 { DialogService } from "@bitwarden/components"; @@ -11,7 +12,7 @@ import { DialogService } from "@bitwarden/components"; import { AccessPolicySelectorService } from "../../shared/access-policies/access-policy-selector/access-policy-selector.service"; import { ApItemValueType, - convertToProjectPeopleAccessPoliciesView, + convertToPeopleAccessPoliciesView, } from "../../shared/access-policies/access-policy-selector/models/ap-item-value.type"; import { ApItemViewType, @@ -38,8 +39,9 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy { }), ), catchError(async () => { + this.logService.info("Error fetching project people access policies."); await this.router.navigate(["/sm", this.organizationId, "projects"]); - return []; + return undefined; }), ); @@ -70,6 +72,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private accessPolicySelectorService: AccessPolicySelectorService, + private logService: LogService, ) {} ngOnInit(): void { @@ -116,10 +119,7 @@ export class ProjectPeopleComponent implements OnInit, OnDestroy { } try { - const projectPeopleView = convertToProjectPeopleAccessPoliciesView( - this.projectId, - formValues, - ); + const projectPeopleView = convertToPeopleAccessPoliciesView(formValues); const peoplePoliciesViews = await this.accessPolicyService.putProjectPeopleAccessPolicies( this.projectId, projectPeopleView, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html index 980f38ca157..1ab8b7e0196 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.html @@ -14,6 +14,7 @@ (deleteSecretsEvent)="openDeleteSecret($event)" (newSecretEvent)="openNewSecretDialog()" (editSecretEvent)="openEditSecret($event)" + (viewSecretEvent)="openViewSecret($event)" (copySecretNameEvent)="copySecretName($event)" (copySecretValueEvent)="copySecretValue($event)" (copySecretUuidEvent)="copySecretUuid($event)" 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 07d50b28ee1..b766de1ebd0 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 @@ -4,6 +4,7 @@ import { combineLatest, combineLatestWith, filter, Observable, startWith, switch import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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"; @@ -18,6 +19,10 @@ import { SecretDialogComponent, SecretOperation, } from "../../secrets/dialog/secret-dialog.component"; +import { + SecretViewDialogComponent, + SecretViewDialogParams, +} from "../../secrets/dialog/secret-view-dialog.component"; import { SecretService } from "../../secrets/secret.service"; import { SecretsListComponent } from "../../shared/secrets-list.component"; import { ProjectService } from "../project.service"; @@ -42,6 +47,7 @@ export class ProjectSecretsComponent { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private organizationService: OrganizationService, + private logService: LogService, ) {} ngOnInit() { @@ -86,6 +92,15 @@ export class ProjectSecretsComponent { }); } + openViewSecret(secretId: string) { + this.dialogService.open(SecretViewDialogComponent, { + data: { + organizationId: this.organizationId, + secretId: secretId, + }, + }); + } + openDeleteSecret(event: SecretListView[]) { this.dialogService.open(SecretDeleteDialogComponent, { data: { @@ -109,12 +124,13 @@ export class ProjectSecretsComponent { SecretsListComponent.copySecretName(name, this.platformUtilsService, this.i18nService); } - copySecretValue(id: string) { - SecretsListComponent.copySecretValue( + async copySecretValue(id: string) { + await SecretsListComponent.copySecretValue( id, this.platformUtilsService, this.i18nService, this.secretService, + this.logService, ); } 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 7ac111ef620..1069033b57d 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 @@ -144,7 +144,7 @@ export class ProjectServiceAccountsComponent implements OnInit, OnDestroy { projectId: string, selectedPolicies: ApItemValueType[], ): Promise { - const view = convertToProjectServiceAccountsAccessPoliciesView(projectId, selectedPolicies); + const view = convertToProjectServiceAccountsAccessPoliciesView(selectedPolicies); return await this.accessPolicyService.putProjectServiceAccountsAccessPolicies( organizationId, projectId, 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 742c2bea1d8..07ca32600a9 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,9 +1,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { - catchError, combineLatest, - EMPTY, filter, Observable, startWith, @@ -58,18 +56,6 @@ export class ProjectComponent implements OnInit, OnDestroy { this.project$ = combineLatest([this.route.params, currentProjectEdited]).pipe( switchMap(([params, _]) => this.projectService.getByProjectId(params.projectId)), - catchError(() => { - // 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(["/sm", this.organizationId, "projects"]).then(() => { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("notFound", this.i18nService.t("project")), - ); - }); - return EMPTY; - }), ); const projectId$ = this.route.params.pipe(map((p) => p.projectId)); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts index 6078520989a..231486703c9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects-routing.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; +import { projectAccessGuard } from "./guards/project-access.guard"; import { ProjectPeopleComponent } from "./project/project-people.component"; import { ProjectSecretsComponent } from "./project/project-secrets.component"; import { ProjectServiceAccountsComponent } from "./project/project-service-accounts.component"; @@ -15,6 +16,7 @@ const routes: Routes = [ { path: ":projectId", component: ProjectComponent, + canActivate: [projectAccessGuard], children: [ { path: "", 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 b1bd91a04fb..0287cdd4251 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 @@ -199,7 +199,7 @@ export class SecretDialogComponent implements OnInit { return await this.projectService.create(this.data.organizationId, projectView); } - protected openDeleteSecretDialog() { + protected async openDeleteSecretDialog() { const secretListView: SecretListView[] = this.getSecretListView(); const dialogRef = this.dialogService.open( @@ -212,9 +212,7 @@ export class SecretDialogComponent implements OnInit { ); // If the secret is deleted, chain close this dialog after the delete dialog - // 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 - lastValueFrom(dialogRef.closed).then( + await lastValueFrom(dialogRef.closed).then( (closeData) => closeData !== undefined && this.dialogRef.close(), ); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.html new file mode 100644 index 00000000000..60fa1f268c2 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.html @@ -0,0 +1,30 @@ +
      + + +
      + + {{ "name" | i18n }} + + + + {{ "value" | i18n }} + + +
      + + {{ "notes" | i18n }} + + +
      + + + +
      +
      diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts new file mode 100644 index 00000000000..a113fd2ffa4 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts @@ -0,0 +1,39 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { Component, Inject, OnInit } from "@angular/core"; +import { FormControl, FormGroup } from "@angular/forms"; + +import { SecretService } from "../secret.service"; + +export interface SecretViewDialogParams { + organizationId: string; + secretId: string; +} + +@Component({ + templateUrl: "./secret-view-dialog.component.html", +}) +export class SecretViewDialogComponent implements OnInit { + protected loading = true; + protected formGroup = new FormGroup({ + name: new FormControl(""), + value: new FormControl(""), + notes: new FormControl(""), + }); + + constructor( + private secretService: SecretService, + @Inject(DIALOG_DATA) private params: SecretViewDialogParams, + ) {} + + async ngOnInit() { + this.loading = true; + const secret = await this.secretService.getBySecretId(this.params.secretId); + this.formGroup.setValue({ + name: secret.name, + value: secret.value, + notes: secret.note, + }); + this.formGroup.disable(); + this.loading = false; + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html index 13595d97205..b12f5c9f184 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.html @@ -10,6 +10,7 @@ (deleteSecretsEvent)="openDeleteSecret($event)" (newSecretEvent)="openNewSecretDialog()" (editSecretEvent)="openEditSecret($event)" + (viewSecretEvent)="openViewSecret($event)" (copySecretNameEvent)="copySecretName($event)" (copySecretValueEvent)="copySecretValue($event)" (copySecretUuidEvent)="copySecretUuid($event)" 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 a7413c9b59f..1744e970361 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 @@ -4,6 +4,7 @@ import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; 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"; @@ -19,6 +20,10 @@ import { SecretDialogComponent, SecretOperation, } from "./dialog/secret-dialog.component"; +import { + SecretViewDialogComponent, + SecretViewDialogParams, +} from "./dialog/secret-view-dialog.component"; import { SecretService } from "./secret.service"; @Component({ @@ -39,6 +44,7 @@ export class SecretsComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private organizationService: OrganizationService, + private logService: LogService, ) {} ngOnInit() { @@ -75,6 +81,15 @@ export class SecretsComponent implements OnInit { }); } + openViewSecret(secretId: string) { + this.dialogService.open(SecretViewDialogComponent, { + data: { + organizationId: this.organizationId, + secretId: secretId, + }, + }); + } + openDeleteSecret(event: SecretListView[]) { this.dialogService.open(SecretDeleteDialogComponent, { data: { @@ -97,12 +112,13 @@ export class SecretsComponent implements OnInit { SecretsListComponent.copySecretName(name, this.platformUtilsService, this.i18nService); } - copySecretValue(id: string) { - SecretsListComponent.copySecretValue( + async copySecretValue(id: string) { + await SecretsListComponent.copySecretValue( id, this.platformUtilsService, this.i18nService, this.secretService, + this.logService, ); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts index 356021817b4..2ae5bfa3b8e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.module.ts @@ -4,12 +4,18 @@ import { SecretsManagerSharedModule } from "../shared/sm-shared.module"; import { SecretDeleteDialogComponent } from "./dialog/secret-delete.component"; import { SecretDialogComponent } from "./dialog/secret-dialog.component"; +import { SecretViewDialogComponent } from "./dialog/secret-view-dialog.component"; import { SecretsRoutingModule } from "./secrets-routing.module"; import { SecretsComponent } from "./secrets.component"; @NgModule({ imports: [SecretsManagerSharedModule, SecretsRoutingModule], - declarations: [SecretDeleteDialogComponent, SecretDialogComponent, SecretsComponent], + declarations: [ + SecretDeleteDialogComponent, + SecretDialogComponent, + SecretViewDialogComponent, + SecretsComponent, + ], providers: [], }) export class SecretsModule {} 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 105ca59e57f..de753d88138 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 @@ -47,9 +47,7 @@ export class ServiceAccountDialogComponent { async ngOnInit() { if (this.data.operation == OperationType.Edit) { - // 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.loadData(); + await this.loadData(); } } 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 new file mode 100644 index 00000000000..956935ac6ac --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -0,0 +1,122 @@ +import { Component } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { Router } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { MockProxy, mock } from "jest-mock-extended"; + +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +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 { RouterService } from "../../../../../../../../clients/apps/web/src/app/core/router.service"; +import { ServiceAccountView } from "../../models/view/service-account.view"; +import { ServiceAccountService } from "../service-account.service"; + +import { serviceAccountAccessGuard } from "./service-account-access.guard"; + +@Component({ + template: "", +}) +export class GuardedRouteTestComponent {} + +@Component({ + template: "", +}) +export class RedirectTestComponent {} + +describe("Service account Redirect Guard", () => { + let organizationService: MockProxy; + let routerService: MockProxy; + let serviceAccountServiceMock: MockProxy; + let i18nServiceMock: MockProxy; + let platformUtilsService: MockProxy; + let router: Router; + + const smOrg1 = { id: "123", canAccessSecretsManager: true } as Organization; + const serviceAccountView = { + id: "123", + organizationId: "123", + name: "service-account-name", + } as ServiceAccountView; + + beforeEach(async () => { + organizationService = mock(); + routerService = mock(); + serviceAccountServiceMock = mock(); + i18nServiceMock = mock(); + platformUtilsService = mock(); + + TestBed.configureTestingModule({ + imports: [ + RouterTestingModule.withRoutes([ + { + path: "sm/:organizationId/machine-accounts/:serviceAccountId", + component: GuardedRouteTestComponent, + canActivate: [serviceAccountAccessGuard], + }, + { + path: "sm", + component: RedirectTestComponent, + }, + { + path: "sm/:organizationId/machine-accounts", + component: RedirectTestComponent, + }, + ]), + ], + providers: [ + { provide: OrganizationService, useValue: organizationService }, + { provide: RouterService, useValue: routerService }, + { provide: ServiceAccountService, useValue: serviceAccountServiceMock }, + { provide: I18nService, useValue: i18nServiceMock }, + { provide: PlatformUtilsService, useValue: platformUtilsService }, + ], + }); + + router = TestBed.inject(Router); + }); + + it("redirects to sm/{orgId}/machine-accounts/{serviceAccountId} if machine account exists", async () => { + // Arrange + organizationService.getAll.mockResolvedValue([smOrg1]); + serviceAccountServiceMock.getByServiceAccountId.mockReturnValue( + Promise.resolve(serviceAccountView), + ); + + // Act + await router.navigateByUrl("sm/123/machine-accounts/123"); + + // Assert + expect(router.url).toBe("/sm/123/machine-accounts/123"); + }); + + it("redirects to sm/machine-accounts if machine account does not exist", async () => { + // Arrange + organizationService.getAll.mockResolvedValue([smOrg1]); + + // Act + await router.navigateByUrl("sm/123/machine-accounts/124"); + + // Assert + expect(router.url).toBe("/sm/123/machine-accounts"); + }); + + it("redirects to sm/123/machine-accounts if exception occurs while looking for service account", async () => { + // Arrange + jest.spyOn(serviceAccountServiceMock, "getByServiceAccountId").mockImplementation(() => { + throw new Error("Test error"); + }); + jest.spyOn(i18nServiceMock, "t").mockReturnValue("Service account not found"); + + // Act + await router.navigateByUrl("sm/123/machine-accounts/123"); + // Assert + expect(platformUtilsService.showToast).toHaveBeenCalledWith( + "error", + null, + "Service account not found", + ); + expect(router.url).toBe("/sm/123/machine-accounts"); + }); +}); 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 c474ec44d55..b72fc5a1fe2 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,6 +1,9 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn, createUrlTreeFromSnapshot } from "@angular/router"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + import { ServiceAccountService } from "../service-account.service"; /** @@ -8,6 +11,8 @@ import { ServiceAccountService } from "../service-account.service"; */ export const serviceAccountAccessGuard: CanActivateFn = async (route: ActivatedRouteSnapshot) => { const serviceAccountService = inject(ServiceAccountService); + const platformUtilsService = inject(PlatformUtilsService); + const i18nService = inject(I18nService); try { const serviceAccount = await serviceAccountService.getByServiceAccountId( @@ -18,6 +23,12 @@ export const serviceAccountAccessGuard: CanActivateFn = async (route: ActivatedR return true; } } catch { + platformUtilsService.showToast( + "error", + null, + i18nService.t("notFound", i18nService.t("machineAccount")), + ); + return createUrlTreeFromSnapshot(route, [ "/sm", route.params.organizationId, 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 a3d3984ea82..31394f7fec7 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 @@ -11,7 +11,7 @@ import { DialogService } from "@bitwarden/components"; import { AccessPolicySelectorService } from "../../shared/access-policies/access-policy-selector/access-policy-selector.service"; import { ApItemValueType, - convertToServiceAccountPeopleAccessPoliciesView, + convertToPeopleAccessPoliciesView, } from "../../shared/access-policies/access-policy-selector/models/ap-item-value.type"; import { ApItemViewType, @@ -180,10 +180,7 @@ export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { serviceAccountId: string, selectedPolicies: ApItemValueType[], ) { - const serviceAccountPeopleView = convertToServiceAccountPeopleAccessPoliciesView( - serviceAccountId, - selectedPolicies, - ); + const serviceAccountPeopleView = convertToPeopleAccessPoliciesView(selectedPolicies); return await this.accessPolicyService.putServiceAccountPeopleAccessPolicies( serviceAccountId, serviceAccountPeopleView, 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 49ddfe331fb..358a152b6f3 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 @@ -144,10 +144,7 @@ export class ServiceAccountProjectsComponent implements OnInit, OnDestroy { serviceAccountId: string, selectedPolicies: ApItemValueType[], ): Promise { - const grantedViews = convertToServiceAccountGrantedPoliciesView( - serviceAccountId, - selectedPolicies, - ); + const grantedViews = convertToServiceAccountGrantedPoliciesView(selectedPolicies); return await this.accessPolicyService.putServiceAccountGrantedPolicies( organizationId, serviceAccountId, 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 bb687c51c62..51b663acce6 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,15 +1,6 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { - EMPTY, - Subject, - catchError, - combineLatest, - filter, - startWith, - switchMap, - takeUntil, -} from "rxjs"; +import { Subject, combineLatest, filter, startWith, switchMap, takeUntil } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -42,18 +33,6 @@ export class ServiceAccountComponent implements OnInit, OnDestroy { params.organizationId, ), ), - catchError(() => { - // 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(["/sm", this.organizationId, "machine-accounts"]).then(() => { - this.platformUtilsService.showToast( - "error", - null, - this.i18nService.t("notFound", this.i18nService.t("machineAccount")), - ); - }); - return EMPTY; - }), ); constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html index e926ba6a13d..454b497fcdb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.html @@ -78,9 +78,11 @@ -
      - {{ emptyMessage }} -
      + + + {{ emptyMessage }} + +
      diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.spec.ts new file mode 100644 index 00000000000..ad6564a1c6c --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.spec.ts @@ -0,0 +1,238 @@ +import { + convertToSecretAccessPoliciesView, + convertToPeopleAccessPoliciesView, + ApItemValueType, + convertToProjectServiceAccountsAccessPoliciesView, + convertToServiceAccountGrantedPoliciesView, +} from "./ap-item-value.type"; +import { ApItemEnum } from "./enums/ap-item.enum"; +import { ApPermissionEnum } from "./enums/ap-permission.enum"; + +describe("convertToPeopleAccessPoliciesView", () => { + it("should convert selected policy values to user and group access policies view", () => { + const selectedPolicyValues = [...createUserApItems(), ...createGroupApItems()]; + + const result = convertToPeopleAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual(expectedUserAccessPolicies); + expect(result.groupAccessPolicies).toEqual(expectedGroupAccessPolicies); + }); + + it("should return empty user array if no selected users are provided", () => { + const selectedPolicyValues = createGroupApItems(); + + const result = convertToPeopleAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual([]); + expect(result.groupAccessPolicies).toEqual(expectedGroupAccessPolicies); + }); + + it("should return empty group array if no selected groups are provided", () => { + const selectedPolicyValues = createUserApItems(); + + const result = convertToPeopleAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual(expectedUserAccessPolicies); + expect(result.groupAccessPolicies).toEqual([]); + }); + + it("should return empty arrays if no selected policy values are provided", () => { + const selectedPolicyValues: ApItemValueType[] = []; + + const result = convertToPeopleAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual([]); + expect(result.groupAccessPolicies).toEqual([]); + }); +}); + +describe("convertToServiceAccountGrantedPoliciesView", () => { + it("should convert selected policy values to ServiceAccountGrantedPoliciesView", () => { + const selectedPolicyValues = createProjectApItems(); + + const result = convertToServiceAccountGrantedPoliciesView(selectedPolicyValues); + + expect(result.grantedProjectPolicies).toHaveLength(2); + expect(result.grantedProjectPolicies[0].accessPolicy.grantedProjectId).toBe( + selectedPolicyValues[0].id, + ); + expect(result.grantedProjectPolicies[0].accessPolicy.read).toBe(true); + expect(result.grantedProjectPolicies[0].accessPolicy.write).toBe(false); + + expect(result.grantedProjectPolicies[1].accessPolicy.grantedProjectId).toBe( + selectedPolicyValues[1].id, + ); + expect(result.grantedProjectPolicies[1].accessPolicy.read).toBe(true); + expect(result.grantedProjectPolicies[1].accessPolicy.write).toBe(true); + }); + + it("should return empty array if no selected project policies are provided", () => { + const selectedPolicyValues: ApItemValueType[] = []; + + const result = convertToServiceAccountGrantedPoliciesView(selectedPolicyValues); + + expect(result.grantedProjectPolicies).toEqual([]); + }); +}); + +describe("convertToProjectServiceAccountsAccessPoliciesView", () => { + it("should convert selected policy values to ProjectServiceAccountsAccessPoliciesView", () => { + const selectedPolicyValues = createServiceAccountApItems(); + + const result = convertToProjectServiceAccountsAccessPoliciesView(selectedPolicyValues); + + expect(result.serviceAccountAccessPolicies).toEqual(expectedServiceAccountAccessPolicies); + }); + + it("should return empty array if nothing is selected.", () => { + const selectedPolicyValues: ApItemValueType[] = []; + + const result = convertToProjectServiceAccountsAccessPoliciesView(selectedPolicyValues); + + expect(result.serviceAccountAccessPolicies).toEqual([]); + }); +}); + +describe("convertToSecretAccessPoliciesView", () => { + it("should convert selected policy values to SecretAccessPoliciesView", () => { + const selectedPolicyValues = [ + ...createUserApItems(), + ...createGroupApItems(), + ...createServiceAccountApItems(), + ]; + const result = convertToSecretAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual(expectedUserAccessPolicies); + expect(result.groupAccessPolicies).toEqual(expectedGroupAccessPolicies); + expect(result.serviceAccountAccessPolicies).toEqual(expectedServiceAccountAccessPolicies); + }); + + it("should return empty user array if no selected users are provided", () => { + const selectedPolicyValues = [...createGroupApItems(), ...createServiceAccountApItems()]; + + const result = convertToSecretAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual([]); + expect(result.groupAccessPolicies).toEqual(expectedGroupAccessPolicies); + expect(result.serviceAccountAccessPolicies).toEqual(expectedServiceAccountAccessPolicies); + }); + + it("should return empty group array if no selected groups are provided", () => { + const selectedPolicyValues = [...createUserApItems(), ...createServiceAccountApItems()]; + + const result = convertToSecretAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual(expectedUserAccessPolicies); + expect(result.groupAccessPolicies).toEqual([]); + expect(result.serviceAccountAccessPolicies).toEqual(expectedServiceAccountAccessPolicies); + }); + + it("should return empty service account array if no selected service accounts are provided", () => { + const selectedPolicyValues = [...createUserApItems(), ...createGroupApItems()]; + + const result = convertToSecretAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual(expectedUserAccessPolicies); + expect(result.groupAccessPolicies).toEqual(expectedGroupAccessPolicies); + expect(result.serviceAccountAccessPolicies).toEqual([]); + }); + + it("should return empty arrays if nothing is selected.", () => { + const selectedPolicyValues: ApItemValueType[] = []; + + const result = convertToSecretAccessPoliciesView(selectedPolicyValues); + + expect(result.userAccessPolicies).toEqual([]); + expect(result.groupAccessPolicies).toEqual([]); + expect(result.serviceAccountAccessPolicies).toEqual([]); + }); +}); + +function createUserApItems(): ApItemValueType[] { + return [ + { + id: "1", + type: ApItemEnum.User, + permission: ApPermissionEnum.CanRead, + }, + { + id: "3", + type: ApItemEnum.User, + permission: ApPermissionEnum.CanReadWrite, + }, + ]; +} + +const expectedUserAccessPolicies = [ + { + organizationUserId: "1", + read: true, + write: false, + }, + { + organizationUserId: "3", + read: true, + write: true, + }, +]; + +function createServiceAccountApItems(): ApItemValueType[] { + return [ + { + id: "1", + type: ApItemEnum.ServiceAccount, + permission: ApPermissionEnum.CanRead, + }, + { + id: "2", + type: ApItemEnum.ServiceAccount, + permission: ApPermissionEnum.CanReadWrite, + }, + ]; +} + +const expectedServiceAccountAccessPolicies = [ + { + serviceAccountId: "1", + read: true, + write: false, + }, + { + serviceAccountId: "2", + read: true, + write: true, + }, +]; + +function createGroupApItems(): ApItemValueType[] { + return [ + { + id: "2", + type: ApItemEnum.Group, + permission: ApPermissionEnum.CanReadWrite, + }, + ]; +} + +const expectedGroupAccessPolicies = [ + { + groupId: "2", + read: true, + write: true, + }, +]; + +function createProjectApItems(): ApItemValueType[] { + return [ + { + id: "1", + type: ApItemEnum.Project, + permission: ApPermissionEnum.CanRead, + }, + { + id: "2", + type: ApItemEnum.Project, + permission: ApPermissionEnum.CanReadWrite, + }, + ]; +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.type.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.type.ts index 935c77f1b3b..2de071fb2e8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.type.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-value.type.ts @@ -1,17 +1,15 @@ import { - UserProjectAccessPolicyView, - GroupProjectAccessPolicyView, - UserServiceAccountAccessPolicyView, - GroupServiceAccountAccessPolicyView, - ServiceAccountProjectAccessPolicyView, + UserAccessPolicyView, + GroupAccessPolicyView, + ServiceAccountAccessPolicyView, + GrantedProjectAccessPolicyView, } from "../../../../models/view/access-policies/access-policy.view"; -import { ProjectPeopleAccessPoliciesView } from "../../../../models/view/access-policies/project-people-access-policies.view"; import { ProjectServiceAccountsAccessPoliciesView } from "../../../../models/view/access-policies/project-service-accounts-access-policies.view"; +import { SecretAccessPoliciesView } from "../../../../models/view/access-policies/secret-access-policies.view"; import { ServiceAccountGrantedPoliciesView, - ServiceAccountProjectPolicyPermissionDetailsView, + GrantedProjectPolicyPermissionDetailsView, } from "../../../../models/view/access-policies/service-account-granted-policies.view"; -import { ServiceAccountPeopleAccessPoliciesView } from "../../../../models/view/access-policies/service-account-people-access-policies.view"; import { ApItemEnum } from "./enums/ap-item.enum"; import { ApPermissionEnum, ApPermissionEnumUtil } from "./enums/ap-permission.enum"; @@ -24,67 +22,14 @@ export type ApItemValueType = { currentUser?: boolean; }; -export function convertToProjectPeopleAccessPoliciesView( - projectId: string, - selectedPolicyValues: ApItemValueType[], -): ProjectPeopleAccessPoliciesView { - const view = new ProjectPeopleAccessPoliciesView(); - view.userAccessPolicies = selectedPolicyValues - .filter((x) => x.type == ApItemEnum.User) - .map((filtered) => { - const policyView = new UserProjectAccessPolicyView(); - policyView.grantedProjectId = projectId; - policyView.organizationUserId = filtered.id; - policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); - policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); - return policyView; - }); - - view.groupAccessPolicies = selectedPolicyValues - .filter((x) => x.type == ApItemEnum.Group) - .map((filtered) => { - const policyView = new GroupProjectAccessPolicyView(); - policyView.grantedProjectId = projectId; - policyView.groupId = filtered.id; - policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); - policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); - return policyView; - }); - return view; -} - -export function convertToServiceAccountPeopleAccessPoliciesView( - serviceAccountId: string, - selectedPolicyValues: ApItemValueType[], -): ServiceAccountPeopleAccessPoliciesView { - const view = new ServiceAccountPeopleAccessPoliciesView(); - view.userAccessPolicies = selectedPolicyValues - .filter((x) => x.type == ApItemEnum.User) - .map((filtered) => { - const policyView = new UserServiceAccountAccessPolicyView(); - policyView.grantedServiceAccountId = serviceAccountId; - policyView.organizationUserId = filtered.id; - policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); - policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); - policyView.currentUser = filtered.currentUser; - return policyView; - }); - - view.groupAccessPolicies = selectedPolicyValues - .filter((x) => x.type == ApItemEnum.Group) - .map((filtered) => { - const policyView = new GroupServiceAccountAccessPolicyView(); - policyView.grantedServiceAccountId = serviceAccountId; - policyView.groupId = filtered.id; - policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); - policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); - return policyView; - }); - return view; +export function convertToPeopleAccessPoliciesView(selectedPolicyValues: ApItemValueType[]) { + return { + userAccessPolicies: convertToUserAccessPolicyViews(selectedPolicyValues), + groupAccessPolicies: convertToGroupAccessPolicyViews(selectedPolicyValues), + }; } export function convertToServiceAccountGrantedPoliciesView( - serviceAccountId: string, selectedPolicyValues: ApItemValueType[], ): ServiceAccountGrantedPoliciesView { const view = new ServiceAccountGrantedPoliciesView(); @@ -92,9 +37,8 @@ export function convertToServiceAccountGrantedPoliciesView( view.grantedProjectPolicies = selectedPolicyValues .filter((x) => x.type == ApItemEnum.Project) .map((filtered) => { - const detailView = new ServiceAccountProjectPolicyPermissionDetailsView(); - const policyView = new ServiceAccountProjectAccessPolicyView(); - policyView.serviceAccountId = serviceAccountId; + const detailView = new GrantedProjectPolicyPermissionDetailsView(); + const policyView = new GrantedProjectAccessPolicyView(); policyView.grantedProjectId = filtered.id; policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); @@ -107,21 +51,57 @@ export function convertToServiceAccountGrantedPoliciesView( } export function convertToProjectServiceAccountsAccessPoliciesView( - projectId: string, selectedPolicyValues: ApItemValueType[], ): ProjectServiceAccountsAccessPoliciesView { - const view = new ProjectServiceAccountsAccessPoliciesView(); + return { + serviceAccountAccessPolicies: convertToServiceAccountAccessPolicyViews(selectedPolicyValues), + }; +} - view.serviceAccountAccessPolicies = selectedPolicyValues - .filter((x) => x.type == ApItemEnum.ServiceAccount) +export function convertToSecretAccessPoliciesView( + selectedPolicyValues: ApItemValueType[], +): SecretAccessPoliciesView { + return { + userAccessPolicies: convertToUserAccessPolicyViews(selectedPolicyValues), + groupAccessPolicies: convertToGroupAccessPolicyViews(selectedPolicyValues), + serviceAccountAccessPolicies: convertToServiceAccountAccessPolicyViews(selectedPolicyValues), + }; +} + +function convertToUserAccessPolicyViews(apItemValues: ApItemValueType[]): UserAccessPolicyView[] { + return apItemValues + .filter((x) => x.type == ApItemEnum.User) .map((filtered) => { - const policyView = new ServiceAccountProjectAccessPolicyView(); - policyView.serviceAccountId = filtered.id; - policyView.grantedProjectId = projectId; + const policyView = new UserAccessPolicyView(); + policyView.organizationUserId = filtered.id; + policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); + policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); + return policyView; + }); +} + +function convertToGroupAccessPolicyViews(apItemValues: ApItemValueType[]): GroupAccessPolicyView[] { + return apItemValues + .filter((x) => x.type == ApItemEnum.Group) + .map((filtered) => { + const policyView = new GroupAccessPolicyView(); + policyView.groupId = filtered.id; + policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); + policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); + return policyView; + }); +} + +function convertToServiceAccountAccessPolicyViews( + apItemValues: ApItemValueType[], +): ServiceAccountAccessPolicyView[] { + return apItemValues + .filter((x) => x.type == ApItemEnum.ServiceAccount) + .map((filtered) => { + const policyView = new ServiceAccountAccessPolicyView(); + policyView.serviceAccountId = filtered.id; policyView.read = ApPermissionEnumUtil.toRead(filtered.permission); policyView.write = ApPermissionEnumUtil.toWrite(filtered.permission); return policyView; }); - - return view; } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.spec.ts new file mode 100644 index 00000000000..c7090391211 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/ap-item-view.spec.ts @@ -0,0 +1,310 @@ +import { + GroupAccessPolicyView, + ServiceAccountAccessPolicyView, + UserAccessPolicyView, +} from "../../../../models/view/access-policies/access-policy.view"; +import { ProjectPeopleAccessPoliciesView } from "../../../../models/view/access-policies/project-people-access-policies.view"; +import { ProjectServiceAccountsAccessPoliciesView } from "../../../../models/view/access-policies/project-service-accounts-access-policies.view"; +import { SecretAccessPoliciesView } from "../../../../models/view/access-policies/secret-access-policies.view"; +import { ServiceAccountGrantedPoliciesView } from "../../../../models/view/access-policies/service-account-granted-policies.view"; +import { ServiceAccountPeopleAccessPoliciesView } from "../../../../models/view/access-policies/service-account-people-access-policies.view"; + +import { + convertGrantedPoliciesToAccessPolicyItemViews, + convertProjectServiceAccountsViewToApItemViews, + convertSecretAccessPoliciesToApItemViews, + convertToAccessPolicyItemViews, +} from "./ap-item-view.type"; +import { ApItemEnum } from "./enums/ap-item.enum"; +import { ApPermissionEnum } from "./enums/ap-permission.enum"; + +describe("convertToAccessPolicyItemViews", () => { + it("should convert ProjectPeopleAccessPoliciesView to ApItemViewType array", () => { + const accessPoliciesView: ProjectPeopleAccessPoliciesView = createPeopleAccessPoliciesView(); + + const result = convertToAccessPolicyItemViews(accessPoliciesView); + + expect(result).toEqual([...expectedUserApItemViews, ...expectedGroupApItemViews]); + }); + + it("should convert empty ProjectPeopleAccessPoliciesView to empty ApItemViewType array", () => { + const accessPoliciesView = new ProjectPeopleAccessPoliciesView(); + accessPoliciesView.userAccessPolicies = []; + accessPoliciesView.groupAccessPolicies = []; + + const result = convertToAccessPolicyItemViews(accessPoliciesView); + + expect(result).toEqual([]); + }); + + it("should convert ServiceAccountPeopleAccessPoliciesView to ApItemViewType array", () => { + const accessPoliciesView: ServiceAccountPeopleAccessPoliciesView = + createPeopleAccessPoliciesView(); + + const result = convertToAccessPolicyItemViews(accessPoliciesView); + + expect(result).toEqual([...expectedUserApItemViews, ...expectedGroupApItemViews]); + }); + + it("should convert empty ServiceAccountPeopleAccessPoliciesView to empty ApItemViewType array", () => { + const accessPoliciesView = new ServiceAccountPeopleAccessPoliciesView(); + accessPoliciesView.userAccessPolicies = []; + accessPoliciesView.groupAccessPolicies = []; + + const result = convertToAccessPolicyItemViews(accessPoliciesView); + + expect(result).toEqual([]); + }); +}); + +describe("convertGrantedPoliciesToAccessPolicyItemViews", () => { + it("should convert ServiceAccountGrantedPoliciesView to ApItemViewType array", () => { + const grantedPoliciesView: ServiceAccountGrantedPoliciesView = createGrantedPoliciesView(); + + const result = convertGrantedPoliciesToAccessPolicyItemViews(grantedPoliciesView); + + expect(result).toEqual(expectedGrantedProjectApItemViews); + }); + + it("should convert empty ServiceAccountGrantedPoliciesView to empty ApItemViewType array", () => { + const grantedPoliciesView = new ServiceAccountGrantedPoliciesView(); + grantedPoliciesView.grantedProjectPolicies = []; + + const result = convertGrantedPoliciesToAccessPolicyItemViews(grantedPoliciesView); + + expect(result).toEqual([]); + }); +}); + +describe("convertProjectServiceAccountsViewToApItemViews", () => { + it("should convert ProjectServiceAccountsAccessPoliciesView to ApItemViewType array", () => { + const accessPoliciesView = createProjectServiceAccountsAccessPoliciesView(); + + const result = convertProjectServiceAccountsViewToApItemViews(accessPoliciesView); + + expect(result).toEqual([...expectedServiceAccountAccessPolicyViews]); + }); + + it("should convert empty ProjectPeopleAccessPoliciesView to empty ApItemViewType array", () => { + const accessPoliciesView = new ProjectServiceAccountsAccessPoliciesView(); + accessPoliciesView.serviceAccountAccessPolicies = []; + + const result = convertProjectServiceAccountsViewToApItemViews(accessPoliciesView); + + expect(result).toEqual([]); + }); +}); + +describe("convertSecretAccessPoliciesToApItemViews", () => { + it("should convert SecretAccessPoliciesView to ApItemViewType array", () => { + const accessPoliciesView = createSecretAccessPoliciesView(); + + const result = convertSecretAccessPoliciesToApItemViews(accessPoliciesView); + + expect(result).toEqual([ + ...expectedUserApItemViews, + ...expectedGroupApItemViews, + ...expectedServiceAccountAccessPolicyViews, + ]); + }); + + it("should convert empty SecretAccessPoliciesView to empty ApItemViewType array", () => { + const accessPoliciesView = new SecretAccessPoliciesView(); + accessPoliciesView.userAccessPolicies = []; + accessPoliciesView.groupAccessPolicies = []; + accessPoliciesView.serviceAccountAccessPolicies = []; + + const result = convertSecretAccessPoliciesToApItemViews(accessPoliciesView); + + expect(result).toEqual([]); + }); +}); + +function createUserAccessPolicyViews(): UserAccessPolicyView[] { + return [ + { + organizationUserId: "1", + organizationUserName: "Example organization user name", + read: true, + write: false, + currentUser: true, + }, + { + organizationUserId: "2", + organizationUserName: "Example organization user name", + read: true, + write: true, + currentUser: false, + }, + ]; +} + +const expectedUserApItemViews = [ + { + type: ApItemEnum.User, + icon: "bwi-user", + id: "1", + labelName: "Example organization user name", + listName: "Example organization user name", + permission: ApPermissionEnum.CanRead, + currentUser: true, + readOnly: false, + }, + { + type: ApItemEnum.User, + icon: "bwi-user", + id: "2", + labelName: "Example organization user name", + listName: "Example organization user name", + permission: ApPermissionEnum.CanReadWrite, + currentUser: false, + readOnly: false, + }, +]; + +function createGroupAccessPolicyViews(): GroupAccessPolicyView[] { + return [ + { + groupId: "3", + groupName: "Example group name", + currentUserInGroup: true, + read: true, + write: false, + }, + { + groupId: "4", + groupName: "Example group name", + currentUserInGroup: false, + read: true, + write: true, + }, + ]; +} + +const expectedGroupApItemViews = [ + { + type: ApItemEnum.Group, + icon: "bwi-family", + id: "3", + labelName: "Example group name", + listName: "Example group name", + permission: ApPermissionEnum.CanRead, + currentUserInGroup: true, + readOnly: false, + }, + { + type: ApItemEnum.Group, + icon: "bwi-family", + id: "4", + labelName: "Example group name", + listName: "Example group name", + permission: ApPermissionEnum.CanReadWrite, + currentUserInGroup: false, + readOnly: false, + }, +]; + +function createServiceAccountAccessPolicyViews(): ServiceAccountAccessPolicyView[] { + return [ + { + serviceAccountId: "5", + serviceAccountName: "service account name", + read: true, + write: false, + }, + { + serviceAccountId: "6", + serviceAccountName: "service account name", + read: true, + write: true, + }, + ]; +} + +const expectedServiceAccountAccessPolicyViews = [ + { + type: ApItemEnum.ServiceAccount, + icon: "bwi-wrench", + id: "5", + labelName: "service account name", + listName: "service account name", + permission: ApPermissionEnum.CanRead, + readOnly: false, + }, + { + type: ApItemEnum.ServiceAccount, + icon: "bwi-wrench", + id: "6", + labelName: "service account name", + listName: "service account name", + permission: ApPermissionEnum.CanReadWrite, + readOnly: false, + }, +]; + +function createGrantedPoliciesView() { + return { + grantedProjectPolicies: [ + { + accessPolicy: { + grantedProjectId: "1", + grantedProjectName: "Example project name", + read: true, + write: false, + }, + hasPermission: true, + }, + { + accessPolicy: { + grantedProjectId: "2", + grantedProjectName: "project name", + read: true, + write: true, + }, + hasPermission: false, + }, + ], + }; +} + +const expectedGrantedProjectApItemViews = [ + { + type: ApItemEnum.Project, + icon: "bwi-collection", + id: "1", + labelName: "Example project name", + listName: "Example project name", + permission: ApPermissionEnum.CanRead, + readOnly: false, + }, + { + type: ApItemEnum.Project, + icon: "bwi-collection", + id: "2", + labelName: "project name", + listName: "project name", + permission: ApPermissionEnum.CanReadWrite, + readOnly: true, + }, +]; + +function createPeopleAccessPoliciesView() { + return { + userAccessPolicies: createUserAccessPolicyViews(), + groupAccessPolicies: createGroupAccessPolicyViews(), + }; +} + +function createProjectServiceAccountsAccessPoliciesView(): ProjectServiceAccountsAccessPoliciesView { + return { + serviceAccountAccessPolicies: createServiceAccountAccessPolicyViews(), + }; +} + +function createSecretAccessPoliciesView(): SecretAccessPoliciesView { + return { + userAccessPolicies: createUserAccessPolicyViews(), + groupAccessPolicies: createGroupAccessPolicyViews(), + serviceAccountAccessPolicies: createServiceAccountAccessPolicyViews(), + }; +} 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 1a023659c16..52a91c5fd6d 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,9 +1,15 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SelectItemView } from "@bitwarden/components"; +import { + GroupAccessPolicyView, + ServiceAccountAccessPolicyView, + UserAccessPolicyView, +} from "../../../../models/view/access-policies/access-policy.view"; import { PotentialGranteeView } from "../../../../models/view/access-policies/potential-grantee.view"; import { ProjectPeopleAccessPoliciesView } from "../../../../models/view/access-policies/project-people-access-policies.view"; import { ProjectServiceAccountsAccessPoliciesView } from "../../../../models/view/access-policies/project-service-accounts-access-policies.view"; +import { SecretAccessPoliciesView } from "../../../../models/view/access-policies/secret-access-policies.view"; import { ServiceAccountGrantedPoliciesView } from "../../../../models/view/access-policies/service-account-granted-policies.view"; import { ServiceAccountPeopleAccessPoliciesView } from "../../../../models/view/access-policies/service-account-people-access-policies.view"; @@ -11,7 +17,6 @@ import { ApItemEnum, ApItemEnumUtil } from "./enums/ap-item.enum"; import { ApPermissionEnum, ApPermissionEnumUtil } from "./enums/ap-permission.enum"; export type ApItemViewType = SelectItemView & { - accessPolicyId?: string; permission?: ApPermissionEnum; /** * Flag that this item cannot be modified. @@ -22,7 +27,6 @@ export type ApItemViewType = SelectItemView & { } & ( | { type: ApItemEnum.User; - userId?: string; currentUser?: boolean; } | { @@ -40,38 +44,10 @@ export type ApItemViewType = SelectItemView & { export function convertToAccessPolicyItemViews( value: ProjectPeopleAccessPoliciesView | ServiceAccountPeopleAccessPoliciesView, ): ApItemViewType[] { - const accessPolicies: ApItemViewType[] = []; - - value.userAccessPolicies.forEach((policy) => { - accessPolicies.push({ - type: ApItemEnum.User, - icon: ApItemEnumUtil.itemIcon(ApItemEnum.User), - id: policy.organizationUserId, - accessPolicyId: policy.id, - labelName: policy.organizationUserName, - listName: policy.organizationUserName, - permission: ApPermissionEnumUtil.toApPermissionEnum(policy.read, policy.write), - userId: policy.userId, - currentUser: policy.currentUser, - readOnly: false, - }); - }); - - value.groupAccessPolicies.forEach((policy) => { - accessPolicies.push({ - type: ApItemEnum.Group, - icon: ApItemEnumUtil.itemIcon(ApItemEnum.Group), - id: policy.groupId, - accessPolicyId: policy.id, - labelName: policy.groupName, - listName: policy.groupName, - permission: ApPermissionEnumUtil.toApPermissionEnum(policy.read, policy.write), - currentUserInGroup: policy.currentUserInGroup, - readOnly: false, - }); - }); - - return accessPolicies; + return [ + ...toUserApItemViews(value.userAccessPolicies), + ...toGroupApItemViews(value.groupAccessPolicies), + ]; } export function convertGrantedPoliciesToAccessPolicyItemViews( @@ -84,7 +60,6 @@ export function convertGrantedPoliciesToAccessPolicyItemViews( type: ApItemEnum.Project, icon: ApItemEnumUtil.itemIcon(ApItemEnum.Project), id: detailView.accessPolicy.grantedProjectId, - accessPolicyId: detailView.accessPolicy.id, labelName: detailView.accessPolicy.grantedProjectName, listName: detailView.accessPolicy.grantedProjectName, permission: ApPermissionEnumUtil.toApPermissionEnum( @@ -100,24 +75,17 @@ export function convertGrantedPoliciesToAccessPolicyItemViews( export function convertProjectServiceAccountsViewToApItemViews( value: ProjectServiceAccountsAccessPoliciesView, ): ApItemViewType[] { - const accessPolicies: ApItemViewType[] = []; + return toServiceAccountsApItemViews(value.serviceAccountAccessPolicies); +} - value.serviceAccountAccessPolicies.forEach((accessPolicyView) => { - accessPolicies.push({ - type: ApItemEnum.ServiceAccount, - icon: ApItemEnumUtil.itemIcon(ApItemEnum.ServiceAccount), - id: accessPolicyView.serviceAccountId, - accessPolicyId: accessPolicyView.id, - labelName: accessPolicyView.serviceAccountName, - listName: accessPolicyView.serviceAccountName, - permission: ApPermissionEnumUtil.toApPermissionEnum( - accessPolicyView.read, - accessPolicyView.write, - ), - readOnly: false, - }); - }); - return accessPolicies; +export function convertSecretAccessPoliciesToApItemViews( + value: SecretAccessPoliciesView, +): ApItemViewType[] { + return [ + ...toUserApItemViews(value.userAccessPolicies), + ...toGroupApItemViews(value.groupAccessPolicies), + ...toServiceAccountsApItemViews(value.serviceAccountAccessPolicies), + ]; } export function convertPotentialGranteesToApItemViewType( @@ -166,3 +134,49 @@ export function convertPotentialGranteesToApItemViewType( }; }); } + +function toUserApItemViews(policies: UserAccessPolicyView[]): ApItemViewType[] { + return policies.map((policy) => { + return { + type: ApItemEnum.User, + icon: ApItemEnumUtil.itemIcon(ApItemEnum.User), + id: policy.organizationUserId, + labelName: policy.organizationUserName, + listName: policy.organizationUserName, + permission: ApPermissionEnumUtil.toApPermissionEnum(policy.read, policy.write), + currentUser: policy.currentUser, + readOnly: false, + }; + }); +} + +function toGroupApItemViews(policies: GroupAccessPolicyView[]): ApItemViewType[] { + return policies.map((policy) => { + return { + type: ApItemEnum.Group, + icon: ApItemEnumUtil.itemIcon(ApItemEnum.Group), + id: policy.groupId, + labelName: policy.groupName, + listName: policy.groupName, + permission: ApPermissionEnumUtil.toApPermissionEnum(policy.read, policy.write), + currentUserInGroup: policy.currentUserInGroup, + readOnly: false, + }; + }); +} + +function toServiceAccountsApItemViews( + policies: ServiceAccountAccessPolicyView[], +): ApItemViewType[] { + return policies.map((policy) => { + return { + type: ApItemEnum.ServiceAccount, + icon: ApItemEnumUtil.itemIcon(ApItemEnum.ServiceAccount), + id: policy.serviceAccountId, + labelName: policy.serviceAccountName, + listName: policy.serviceAccountName, + permission: ApPermissionEnumUtil.toApPermissionEnum(policy.read, policy.write), + readOnly: false, + }; + }); +} 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 32c130647a1..67fb9b19bca 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 @@ -8,18 +8,18 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { - UserProjectAccessPolicyView, - GroupProjectAccessPolicyView, - UserServiceAccountAccessPolicyView, - GroupServiceAccountAccessPolicyView, - ServiceAccountProjectAccessPolicyView, + UserAccessPolicyView, + GroupAccessPolicyView, + ServiceAccountAccessPolicyView, + GrantedProjectAccessPolicyView, } from "../../models/view/access-policies/access-policy.view"; import { PotentialGranteeView } from "../../models/view/access-policies/potential-grantee.view"; import { ProjectPeopleAccessPoliciesView } from "../../models/view/access-policies/project-people-access-policies.view"; import { ProjectServiceAccountsAccessPoliciesView } from "../../models/view/access-policies/project-service-accounts-access-policies.view"; +import { SecretAccessPoliciesView } from "../../models/view/access-policies/secret-access-policies.view"; import { ServiceAccountGrantedPoliciesView, - ServiceAccountProjectPolicyPermissionDetailsView, + GrantedProjectPolicyPermissionDetailsView, } from "../../models/view/access-policies/service-account-granted-policies.view"; import { ServiceAccountPeopleAccessPoliciesView } from "../../models/view/access-policies/service-account-people-access-policies.view"; import { PeopleAccessPoliciesRequest } from "../../shared/access-policies/models/requests/people-access-policies.request"; @@ -28,18 +28,18 @@ import { ServiceAccountGrantedPoliciesRequest } from "../access-policies/models/ import { AccessPolicyRequest } from "./models/requests/access-policy.request"; import { ProjectServiceAccountsAccessPoliciesRequest } from "./models/requests/project-service-accounts-access-policies.request"; import { - GroupServiceAccountAccessPolicyResponse, - UserServiceAccountAccessPolicyResponse, - GroupProjectAccessPolicyResponse, - ServiceAccountProjectAccessPolicyResponse, - UserProjectAccessPolicyResponse, + GroupAccessPolicyResponse, + UserAccessPolicyResponse, + ServiceAccountAccessPolicyResponse, + GrantedProjectAccessPolicyResponse, } from "./models/responses/access-policy.response"; import { PotentialGranteeResponse } from "./models/responses/potential-grantee.response"; import { ProjectPeopleAccessPoliciesResponse } from "./models/responses/project-people-access-policies.response"; import { ProjectServiceAccountsAccessPoliciesResponse } from "./models/responses/project-service-accounts-access-policies.response"; +import { SecretAccessPoliciesResponse } from "./models/responses/secret-access-policies.response"; import { ServiceAccountGrantedPoliciesPermissionDetailsResponse } from "./models/responses/service-account-granted-policies-permission-details.response"; import { ServiceAccountPeopleAccessPoliciesResponse } from "./models/responses/service-account-people-access-policies.response"; -import { ServiceAccountProjectPolicyPermissionDetailsResponse } from "./models/responses/service-account-project-policy-permission-details.response"; +import { GrantedProjectAccessPolicyPermissionDetailsResponse } from "./models/responses/service-account-project-policy-permission-details.response"; @Injectable({ providedIn: "root", @@ -63,7 +63,7 @@ export class AccessPolicyService { ); const results = new ProjectPeopleAccessPoliciesResponse(r); - return this.createProjectPeopleAccessPoliciesView(results); + return this.createPeopleAccessPoliciesView(results); } async putProjectPeopleAccessPolicies( @@ -79,7 +79,7 @@ export class AccessPolicyService { true, ); const results = new ProjectPeopleAccessPoliciesResponse(r); - return this.createProjectPeopleAccessPoliciesView(results); + return this.createPeopleAccessPoliciesView(results); } async getServiceAccountPeopleAccessPolicies( @@ -94,7 +94,7 @@ export class AccessPolicyService { ); const results = new ServiceAccountPeopleAccessPoliciesResponse(r); - return this.createServiceAccountPeopleAccessPoliciesView(results); + return this.createPeopleAccessPoliciesView(results); } async putServiceAccountPeopleAccessPolicies( @@ -110,7 +110,7 @@ export class AccessPolicyService { true, ); const results = new ServiceAccountPeopleAccessPoliciesResponse(r); - return this.createServiceAccountPeopleAccessPoliciesView(results); + return this.createPeopleAccessPoliciesView(results); } async getServiceAccountGrantedPolicies( @@ -181,6 +181,22 @@ export class AccessPolicyService { return await this.createProjectServiceAccountsAccessPoliciesView(result, organizationId); } + async getSecretAccessPolicies( + organizationId: string, + secretId: string, + ): Promise { + const r = await this.apiService.send( + "GET", + "/secrets/" + secretId + "/access-policies", + null, + true, + true, + ); + + const result = new SecretAccessPoliciesResponse(r); + return await this.createSecretAccessPoliciesView(result, organizationId); + } + async getPeoplePotentialGrantees(organizationId: string) { const r = await this.apiService.send( "GET", @@ -223,12 +239,7 @@ export class AccessPolicyService { private getAccessPolicyRequest( granteeId: string, - view: - | UserProjectAccessPolicyView - | UserServiceAccountAccessPolicyView - | GroupProjectAccessPolicyView - | GroupServiceAccountAccessPolicyView - | ServiceAccountProjectAccessPolicyView, + view: UserAccessPolicyView | GroupAccessPolicyView | ServiceAccountAccessPolicyView, ) { const request = new AccessPolicyRequest(); request.granteeId = granteeId; @@ -285,21 +296,79 @@ export class AccessPolicyService { private createBaseAccessPolicyView( response: - | UserProjectAccessPolicyResponse - | UserServiceAccountAccessPolicyResponse - | GroupProjectAccessPolicyResponse - | GroupServiceAccountAccessPolicyResponse - | ServiceAccountProjectAccessPolicyResponse, + | UserAccessPolicyResponse + | GroupAccessPolicyResponse + | ServiceAccountAccessPolicyResponse + | GrantedProjectAccessPolicyResponse, ) { return { - id: response.id, read: response.read, write: response.write, - creationDate: response.creationDate, - revisionDate: response.revisionDate, }; } + private async createGrantedProjectAccessPolicyView( + organizationKey: SymmetricCryptoKey, + response: GrantedProjectAccessPolicyResponse, + ): Promise { + return { + ...this.createBaseAccessPolicyView(response), + grantedProjectId: response.grantedProjectId, + grantedProjectName: response.grantedProjectName + ? await this.encryptService.decryptToUtf8( + new EncString(response.grantedProjectName), + organizationKey, + ) + : null, + }; + } + + private createUserAccessPolicyViews( + responses: UserAccessPolicyResponse[], + ): UserAccessPolicyView[] { + return responses.map((response) => { + return { + ...this.createBaseAccessPolicyView(response), + organizationUserId: response.organizationUserId, + organizationUserName: response.organizationUserName, + currentUser: response.currentUser, + }; + }); + } + + private createGroupAccessPolicyViews( + responses: GroupAccessPolicyResponse[], + ): GroupAccessPolicyView[] { + return responses.map((response) => { + return { + ...this.createBaseAccessPolicyView(response), + groupId: response.groupId, + groupName: response.groupName, + currentUserInGroup: response.currentUserInGroup, + }; + }); + } + + private async createServiceAccountAccessPolicyViews( + orgKey: SymmetricCryptoKey, + responses: ServiceAccountAccessPolicyResponse[], + ): Promise { + return await Promise.all( + responses.map(async (response) => { + return { + ...this.createBaseAccessPolicyView(response), + serviceAccountId: response.serviceAccountId, + serviceAccountName: response.serviceAccountName + ? await this.encryptService.decryptToUtf8( + new EncString(response.serviceAccountName), + orgKey, + ) + : null, + }; + }), + ); + } + private async createPotentialGranteeViews( organizationId: string, results: PotentialGranteeResponse[], @@ -332,137 +401,44 @@ export class AccessPolicyService { ): Promise { const orgKey = await this.getOrganizationKey(organizationId); - const view = new ServiceAccountGrantedPoliciesView(); - view.grantedProjectPolicies = - await this.createServiceAccountProjectPolicyPermissionDetailsViews( + return { + grantedProjectPolicies: await this.createGrantedProjectPolicyPermissionDetailsViews( orgKey, response.grantedProjectPolicies, - ); - return view; + ), + }; } - private async createServiceAccountProjectPolicyPermissionDetailsViews( + private async createGrantedProjectPolicyPermissionDetailsViews( orgKey: SymmetricCryptoKey, - responses: ServiceAccountProjectPolicyPermissionDetailsResponse[], - ): Promise { + responses: GrantedProjectAccessPolicyPermissionDetailsResponse[], + ): Promise { return await Promise.all( responses.map(async (response) => { - return await this.createServiceAccountProjectPolicyPermissionDetailsView(orgKey, response); + return await this.createGrantedProjectPolicyPermissionDetailsView(orgKey, response); }), ); } - private async createServiceAccountProjectPolicyPermissionDetailsView( + private async createGrantedProjectPolicyPermissionDetailsView( orgKey: SymmetricCryptoKey, - response: ServiceAccountProjectPolicyPermissionDetailsResponse, - ): Promise { - const view = new ServiceAccountProjectPolicyPermissionDetailsView(); + response: GrantedProjectAccessPolicyPermissionDetailsResponse, + ): Promise { + const view = new GrantedProjectPolicyPermissionDetailsView(); view.hasPermission = response.hasPermission; - view.accessPolicy = await this.createServiceAccountProjectAccessPolicyView( + view.accessPolicy = await this.createGrantedProjectAccessPolicyView( orgKey, response.accessPolicy, ); return view; } - private createProjectPeopleAccessPoliciesView( - peopleAccessPoliciesResponse: ProjectPeopleAccessPoliciesResponse, - ): ProjectPeopleAccessPoliciesView { - const view = new ProjectPeopleAccessPoliciesView(); - - view.userAccessPolicies = peopleAccessPoliciesResponse.userAccessPolicies.map((ap) => { - return this.createUserProjectAccessPolicyView(ap); - }); - view.groupAccessPolicies = peopleAccessPoliciesResponse.groupAccessPolicies.map((ap) => { - return this.createGroupProjectAccessPolicyView(ap); - }); - return view; - } - - private createServiceAccountPeopleAccessPoliciesView( - response: ServiceAccountPeopleAccessPoliciesResponse, - ): ServiceAccountPeopleAccessPoliciesView { - const view = new ServiceAccountPeopleAccessPoliciesView(); - - view.userAccessPolicies = response.userAccessPolicies.map((ap) => { - return this.createUserServiceAccountAccessPolicyView(ap); - }); - view.groupAccessPolicies = response.groupAccessPolicies.map((ap) => { - return this.createGroupServiceAccountAccessPolicyView(ap); - }); - return view; - } - - private createUserProjectAccessPolicyView( - response: UserProjectAccessPolicyResponse, - ): UserProjectAccessPolicyView { + private createPeopleAccessPoliciesView( + response: ProjectPeopleAccessPoliciesResponse | ServiceAccountPeopleAccessPoliciesResponse, + ) { return { - ...this.createBaseAccessPolicyView(response), - grantedProjectId: response.grantedProjectId, - organizationUserId: response.organizationUserId, - organizationUserName: response.organizationUserName, - userId: response.userId, - currentUser: response.currentUser, - }; - } - - private createGroupProjectAccessPolicyView( - response: GroupProjectAccessPolicyResponse, - ): GroupProjectAccessPolicyView { - return { - ...this.createBaseAccessPolicyView(response), - grantedProjectId: response.grantedProjectId, - groupId: response.groupId, - groupName: response.groupName, - currentUserInGroup: response.currentUserInGroup, - }; - } - - private async createServiceAccountProjectAccessPolicyView( - organizationKey: SymmetricCryptoKey, - response: ServiceAccountProjectAccessPolicyResponse, - ): Promise { - return { - ...this.createBaseAccessPolicyView(response), - grantedProjectId: response.grantedProjectId, - serviceAccountId: response.serviceAccountId, - grantedProjectName: response.grantedProjectName - ? await this.encryptService.decryptToUtf8( - new EncString(response.grantedProjectName), - organizationKey, - ) - : null, - serviceAccountName: response.serviceAccountName - ? await this.encryptService.decryptToUtf8( - new EncString(response.serviceAccountName), - organizationKey, - ) - : null, - }; - } - - private createUserServiceAccountAccessPolicyView( - response: UserServiceAccountAccessPolicyResponse, - ): UserServiceAccountAccessPolicyView { - return { - ...this.createBaseAccessPolicyView(response), - grantedServiceAccountId: response.grantedServiceAccountId, - organizationUserId: response.organizationUserId, - organizationUserName: response.organizationUserName, - userId: response.userId, - currentUser: response.currentUser, - }; - } - - private createGroupServiceAccountAccessPolicyView( - response: GroupServiceAccountAccessPolicyResponse, - ): GroupServiceAccountAccessPolicyView { - return { - ...this.createBaseAccessPolicyView(response), - grantedServiceAccountId: response.grantedServiceAccountId, - groupId: response.groupId, - groupName: response.groupName, - currentUserInGroup: response.currentUserInGroup, + userAccessPolicies: this.createUserAccessPolicyViews(response.userAccessPolicies), + groupAccessPolicies: this.createGroupAccessPolicyViews(response.groupAccessPolicies), }; } @@ -471,13 +447,26 @@ export class AccessPolicyService { organizationId: string, ): Promise { const orgKey = await this.getOrganizationKey(organizationId); + return { + serviceAccountAccessPolicies: await this.createServiceAccountAccessPolicyViews( + orgKey, + response.serviceAccountAccessPolicies, + ), + }; + } - const view = new ProjectServiceAccountsAccessPoliciesView(); - view.serviceAccountAccessPolicies = await Promise.all( - response.serviceAccountAccessPolicies.map(async (ap) => { - return await this.createServiceAccountProjectAccessPolicyView(orgKey, ap); - }), - ); - return view; + private async createSecretAccessPoliciesView( + response: SecretAccessPoliciesResponse, + organizationId: string, + ): Promise { + const orgKey = await this.getOrganizationKey(organizationId); + return { + userAccessPolicies: this.createUserAccessPolicyViews(response.userAccessPolicies), + groupAccessPolicies: this.createGroupAccessPolicyViews(response.groupAccessPolicies), + serviceAccountAccessPolicies: await this.createServiceAccountAccessPolicyViews( + orgKey, + response.serviceAccountAccessPolicies, + ), + }; } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts index ef076f9b592..88399b4e121 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/access-policy.response.ts @@ -1,96 +1,59 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -export class BaseAccessPolicyResponse extends BaseResponse { - id: string; +class BaseAccessPolicyResponse extends BaseResponse { read: boolean; write: boolean; - creationDate: string; - revisionDate: string; constructor(response: any) { super(response); - this.id = this.getResponseProperty("Id"); this.read = this.getResponseProperty("Read"); this.write = this.getResponseProperty("Write"); - this.creationDate = this.getResponseProperty("CreationDate"); - this.revisionDate = this.getResponseProperty("RevisionDate"); } } -export class UserProjectAccessPolicyResponse extends BaseAccessPolicyResponse { +export class UserAccessPolicyResponse extends BaseAccessPolicyResponse { organizationUserId: string; organizationUserName: string; - grantedProjectId: string; - userId: string; currentUser: boolean; constructor(response: any) { super(response); this.organizationUserId = this.getResponseProperty("OrganizationUserId"); this.organizationUserName = this.getResponseProperty("OrganizationUserName"); - this.grantedProjectId = this.getResponseProperty("GrantedProjectId"); - this.userId = this.getResponseProperty("UserId"); this.currentUser = this.getResponseProperty("CurrentUser"); } } -export class UserServiceAccountAccessPolicyResponse extends BaseAccessPolicyResponse { - organizationUserId: string; - organizationUserName: string; - grantedServiceAccountId: string; - userId: string; - currentUser: boolean; - - constructor(response: any) { - super(response); - this.organizationUserId = this.getResponseProperty("OrganizationUserId"); - this.organizationUserName = this.getResponseProperty("OrganizationUserName"); - this.grantedServiceAccountId = this.getResponseProperty("GrantedServiceAccountId"); - this.userId = this.getResponseProperty("UserId"); - this.currentUser = this.getResponseProperty("CurrentUser"); - } -} - -export class GroupProjectAccessPolicyResponse extends BaseAccessPolicyResponse { +export class GroupAccessPolicyResponse extends BaseAccessPolicyResponse { groupId: string; groupName: string; - grantedProjectId: string; currentUserInGroup: boolean; constructor(response: any) { super(response); this.groupId = this.getResponseProperty("GroupId"); this.groupName = this.getResponseProperty("GroupName"); - this.grantedProjectId = this.getResponseProperty("GrantedProjectId"); this.currentUserInGroup = this.getResponseProperty("CurrentUserInGroup"); } } -export class GroupServiceAccountAccessPolicyResponse extends BaseAccessPolicyResponse { - groupId: string; - groupName: string; - grantedServiceAccountId: string; - currentUserInGroup: boolean; - - constructor(response: any) { - super(response); - this.groupId = this.getResponseProperty("GroupId"); - this.groupName = this.getResponseProperty("GroupName"); - this.grantedServiceAccountId = this.getResponseProperty("GrantedServiceAccountId"); - this.currentUserInGroup = this.getResponseProperty("CurrentUserInGroup"); - } -} - -export class ServiceAccountProjectAccessPolicyResponse extends BaseAccessPolicyResponse { +export class ServiceAccountAccessPolicyResponse extends BaseAccessPolicyResponse { serviceAccountId: string; serviceAccountName: string; - grantedProjectId: string; - grantedProjectName: string; constructor(response: any) { super(response); this.serviceAccountId = this.getResponseProperty("ServiceAccountId"); this.serviceAccountName = this.getResponseProperty("ServiceAccountName"); + } +} + +export class GrantedProjectAccessPolicyResponse extends BaseAccessPolicyResponse { + grantedProjectId: string; + grantedProjectName: string; + + constructor(response: any) { + super(response); this.grantedProjectId = this.getResponseProperty("GrantedProjectId"); this.grantedProjectName = this.getResponseProperty("GrantedProjectName"); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-people-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-people-access-policies.response.ts index 3fa0fa652e0..fbd27168074 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-people-access-policies.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-people-access-policies.response.ts @@ -1,23 +1,18 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { - GroupProjectAccessPolicyResponse, - UserProjectAccessPolicyResponse, -} from "./access-policy.response"; +import { GroupAccessPolicyResponse, UserAccessPolicyResponse } from "./access-policy.response"; export class ProjectPeopleAccessPoliciesResponse extends BaseResponse { - userAccessPolicies: UserProjectAccessPolicyResponse[]; - groupAccessPolicies: GroupProjectAccessPolicyResponse[]; + userAccessPolicies: UserAccessPolicyResponse[]; + groupAccessPolicies: GroupAccessPolicyResponse[]; constructor(response: any) { super(response); const userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); - this.userAccessPolicies = userAccessPolicies.map( - (k: any) => new UserProjectAccessPolicyResponse(k), - ); + this.userAccessPolicies = userAccessPolicies.map((k: any) => new UserAccessPolicyResponse(k)); const groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); this.groupAccessPolicies = groupAccessPolicies.map( - (k: any) => new GroupProjectAccessPolicyResponse(k), + (k: any) => new GroupAccessPolicyResponse(k), ); } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-service-accounts-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-service-accounts-access-policies.response.ts index f26a9996ddc..f50be0ca9a7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-service-accounts-access-policies.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/project-service-accounts-access-policies.response.ts @@ -1,15 +1,15 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { ServiceAccountProjectAccessPolicyResponse } from "./access-policy.response"; +import { ServiceAccountAccessPolicyResponse } from "./access-policy.response"; export class ProjectServiceAccountsAccessPoliciesResponse extends BaseResponse { - serviceAccountAccessPolicies: ServiceAccountProjectAccessPolicyResponse[]; + serviceAccountAccessPolicies: ServiceAccountAccessPolicyResponse[]; constructor(response: any) { super(response); const serviceAccountAccessPolicies = this.getResponseProperty("ServiceAccountAccessPolicies"); this.serviceAccountAccessPolicies = serviceAccountAccessPolicies.map( - (k: any) => new ServiceAccountProjectAccessPolicyResponse(k), + (k: any) => new ServiceAccountAccessPolicyResponse(k), ); } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/secret-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/secret-access-policies.response.ts new file mode 100644 index 00000000000..ccfcb02d1d3 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/secret-access-policies.response.ts @@ -0,0 +1,27 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; + +import { + GroupAccessPolicyResponse, + UserAccessPolicyResponse, + ServiceAccountAccessPolicyResponse, +} from "./access-policy.response"; + +export class SecretAccessPoliciesResponse extends BaseResponse { + userAccessPolicies: UserAccessPolicyResponse[]; + groupAccessPolicies: GroupAccessPolicyResponse[]; + serviceAccountAccessPolicies: ServiceAccountAccessPolicyResponse[]; + + constructor(response: any) { + super(response); + const userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); + this.userAccessPolicies = userAccessPolicies.map((k: any) => new UserAccessPolicyResponse(k)); + const groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); + this.groupAccessPolicies = groupAccessPolicies.map( + (k: any) => new GroupAccessPolicyResponse(k), + ); + const serviceAccountAccessPolicies = this.getResponseProperty("ServiceAccountAccessPolicies"); + this.serviceAccountAccessPolicies = serviceAccountAccessPolicies.map( + (k: any) => new ServiceAccountAccessPolicyResponse(k), + ); + } +} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-granted-policies-permission-details.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-granted-policies-permission-details.response.ts index 858a59ff433..8925e92f883 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-granted-policies-permission-details.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-granted-policies-permission-details.response.ts @@ -1,15 +1,15 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { ServiceAccountProjectPolicyPermissionDetailsResponse } from "./service-account-project-policy-permission-details.response"; +import { GrantedProjectAccessPolicyPermissionDetailsResponse } from "./service-account-project-policy-permission-details.response"; export class ServiceAccountGrantedPoliciesPermissionDetailsResponse extends BaseResponse { - grantedProjectPolicies: ServiceAccountProjectPolicyPermissionDetailsResponse[]; + grantedProjectPolicies: GrantedProjectAccessPolicyPermissionDetailsResponse[]; constructor(response: any) { super(response); const grantedProjectPolicies = this.getResponseProperty("GrantedProjectPolicies"); this.grantedProjectPolicies = grantedProjectPolicies.map( - (k: any) => new ServiceAccountProjectPolicyPermissionDetailsResponse(k), + (k: any) => new GrantedProjectAccessPolicyPermissionDetailsResponse(k), ); } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-people-access-policies.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-people-access-policies.response.ts index ca134d9012a..74796a47890 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-people-access-policies.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-people-access-policies.response.ts @@ -1,23 +1,18 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { - GroupServiceAccountAccessPolicyResponse, - UserServiceAccountAccessPolicyResponse, -} from "./access-policy.response"; +import { GroupAccessPolicyResponse, UserAccessPolicyResponse } from "./access-policy.response"; export class ServiceAccountPeopleAccessPoliciesResponse extends BaseResponse { - userAccessPolicies: UserServiceAccountAccessPolicyResponse[]; - groupAccessPolicies: GroupServiceAccountAccessPolicyResponse[]; + userAccessPolicies: UserAccessPolicyResponse[]; + groupAccessPolicies: GroupAccessPolicyResponse[]; constructor(response: any) { super(response); const userAccessPolicies = this.getResponseProperty("UserAccessPolicies"); - this.userAccessPolicies = userAccessPolicies.map( - (k: any) => new UserServiceAccountAccessPolicyResponse(k), - ); + this.userAccessPolicies = userAccessPolicies.map((k: any) => new UserAccessPolicyResponse(k)); const groupAccessPolicies = this.getResponseProperty("GroupAccessPolicies"); this.groupAccessPolicies = groupAccessPolicies.map( - (k: any) => new GroupServiceAccountAccessPolicyResponse(k), + (k: any) => new GroupAccessPolicyResponse(k), ); } } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-project-policy-permission-details.response.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-project-policy-permission-details.response.ts index dbc4fe0727d..9cbc6efed8f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-project-policy-permission-details.response.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/models/responses/service-account-project-policy-permission-details.response.ts @@ -1,9 +1,9 @@ import { BaseResponse } from "@bitwarden/common/models/response/base.response"; -import { ServiceAccountProjectAccessPolicyResponse } from "./access-policy.response"; +import { GrantedProjectAccessPolicyResponse } from "./access-policy.response"; -export class ServiceAccountProjectPolicyPermissionDetailsResponse extends BaseResponse { - accessPolicy: ServiceAccountProjectAccessPolicyResponse; +export class GrantedProjectAccessPolicyPermissionDetailsResponse extends BaseResponse { + accessPolicy: GrantedProjectAccessPolicyResponse; hasPermission: boolean; constructor(response: any) { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html index f8d5d1081e0..859c7417eb8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.html @@ -66,7 +66,7 @@
      -
      @@ -93,8 +93,9 @@ variant="secondary" class="tw-ml-1" [title]="project.name" + maxWidthClass="tw-max-w-60" > - {{ project.name | ellipsis: 32 }} + {{ project.name }} 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 d640114e071..62c4a43379e 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 @@ -3,6 +3,7 @@ import { Component, EventEmitter, Input, OnDestroy, Output } from "@angular/core import { Subject, takeUntil } from "rxjs"; 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 { TableDataSource } from "@bitwarden/components"; @@ -36,6 +37,7 @@ export class SecretsListComponent implements OnDestroy { @Input() trash: boolean; @Output() editSecretEvent = new EventEmitter(); + @Output() viewSecretEvent = new EventEmitter(); @Output() copySecretNameEvent = new EventEmitter(); @Output() copySecretValueEvent = new EventEmitter(); @Output() copySecretUuidEvent = new EventEmitter(); @@ -115,6 +117,14 @@ export class SecretsListComponent implements OnDestroy { return aProjects[0]?.name.localeCompare(bProjects[0].name); }; + protected editSecret(secret: SecretListView) { + if (secret.write) { + this.editSecretEvent.emit(secret.id); + } else { + this.viewSecretEvent.emit(secret.id); + } + } + /** * TODO: Refactor to smart component and remove */ @@ -134,22 +144,24 @@ export class SecretsListComponent implements OnDestroy { /** * TODO: Refactor to smart component and remove */ - static copySecretValue( + static async copySecretValue( id: string, platformUtilsService: PlatformUtilsService, i18nService: I18nService, secretService: SecretService, + logService: LogService, ) { - const value = secretService.getBySecretId(id).then((secret) => secret.value); - // 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 - SecretsListComponent.copyToClipboardAsync(value, platformUtilsService).then(() => { + try { + const value = await secretService.getBySecretId(id).then((secret) => secret.value); + platformUtilsService.copyToClipboard(value); platformUtilsService.showToast( "success", null, i18nService.t("valueCopied", i18nService.t("value")), ); - }); + } catch { + logService.info("Error fetching secret value."); + } } static copySecretUuid( diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 27095e1da91..26f8e655390 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -12,6 +12,9 @@ "@bitwarden/billing": ["../../libs/billing/src"], "@bitwarden/common/*": ["../../libs/common/src/*"], "@bitwarden/components": ["../../libs/components/src"], + "@bitwarden/generator-components": ["../../libs/tools/generator/components/src"], + "@bitwarden/generator-core": ["../../libs/tools/generator/core/src"], + "@bitwarden/generator-extensions": ["../../libs/tools/generator/extensions/src"], "@bitwarden/vault-export-core": [ "../../libs/tools/export/vault-export/vault-export-core/src" ], @@ -19,6 +22,7 @@ "@bitwarden/importer/core": ["../../libs/importer/src"], "@bitwarden/importer/ui": ["../../libs/importer/src/components"], "@bitwarden/platform": ["../../libs/platform/src"], + "@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"], "@bitwarden/vault": ["../../libs/vault/src"], "@bitwarden/web-vault/*": ["../../apps/web/src/*"], diff --git a/jest.config.js b/jest.config.js index e2c50553d8e..f4e97262a39 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,6 +31,7 @@ module.exports = { "/libs/common/jest.config.js", "/libs/components/jest.config.js", "/libs/tools/export/vault-export/vault-export-core/jest.config.js", + "/libs/tools/generator/core/jest.config.js", "/libs/importer/jest.config.js", "/libs/platform/jest.config.js", "/libs/node/jest.config.js", diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts index a1c9e94fb0a..64cd664f1f3 100644 --- a/libs/angular/src/auth/components/lock.component.ts +++ b/libs/angular/src/auth/components/lock.component.ts @@ -33,6 +33,7 @@ import { HashPurpose, 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 } from "@bitwarden/components"; @Directive() @@ -85,6 +86,7 @@ export class LockComponent implements OnInit, OnDestroy { protected accountService: AccountService, protected authService: AuthService, protected kdfConfigService: KdfConfigService, + protected syncService: SyncService, ) {} async ngOnInit() { @@ -318,6 +320,9 @@ export class LockComponent implements OnInit, OnDestroy { } } + // 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) { diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request.component.ts index 401abab3b19..f898fa4ee8d 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request.component.ts @@ -246,7 +246,10 @@ export class LoginViaAuthRequestComponent const deviceIdentifier = await this.appIdService.getAppId(); const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey); - const accessCode = await this.passwordGenerationService.generatePassword({ length: 25 }); + const accessCode = await this.passwordGenerationService.generatePassword({ + type: "password", + length: 25, + }); this.fingerprintPhrase = ( await this.cryptoService.getFingerprint(this.email, this.authRequestKeyPair.publicKey) diff --git a/libs/angular/src/auth/components/login.component.ts b/libs/angular/src/auth/components/login.component.ts index bcdf747406e..cfef72c435c 100644 --- a/libs/angular/src/auth/components/login.component.ts +++ b/libs/angular/src/auth/components/login.component.ts @@ -155,7 +155,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit, if (this.handleCaptchaRequired(response)) { return; - } else if (this.handleMigrateEncryptionKey(response)) { + } else if (await this.handleMigrateEncryptionKey(response)) { return; } else if (response.requiresTwoFactor) { if (this.onSuccessfulLoginTwoFactorNavigate != null) { @@ -218,9 +218,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit, } this.setLoginEmailValues(); - // 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(["/login-with-device"]); + await this.router.navigate(["/login-with-device"]); } async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { @@ -310,7 +308,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit, // Legacy accounts used the master key to encrypt data. Migration is required // but only performed on web - protected handleMigrateEncryptionKey(result: AuthResult): boolean { + protected async handleMigrateEncryptionKey(result: AuthResult): Promise { if (!result.requiresEncryptionKeyMigration) { return false; } diff --git a/libs/angular/src/auth/components/register.component.ts b/libs/angular/src/auth/components/register.component.ts index 2ba76692902..e3197355dc3 100644 --- a/libs/angular/src/auth/components/register.component.ts +++ b/libs/angular/src/auth/components/register.component.ts @@ -78,6 +78,10 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn protected captchaBypassToken: string = null; + // allows for extending classes to modify the register request before sending + // currently used by web to add organization invitation details + protected modifyRegisterRequest: (request: RegisterRequest) => Promise; + constructor( protected formValidationErrorService: FormValidationErrorsService, protected formBuilder: UntypedFormBuilder, @@ -290,10 +294,8 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn kdfConfig.iterations, ); request.keys = new KeysRequest(keys[0], keys[1].encryptedString); - const orgInvite = await this.stateService.getOrganizationInvitation(); - if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) { - request.token = orgInvite.token; - request.organizationUserId = orgInvite.organizationUserId; + if (this.modifyRegisterRequest) { + await this.modifyRegisterRequest(request); } return request; } diff --git a/libs/angular/src/auth/components/update-password.component.ts b/libs/angular/src/auth/components/update-password.component.ts index 3b709b3e7f7..62e0359038c 100644 --- a/libs/angular/src/auth/components/update-password.component.ts +++ b/libs/angular/src/auth/components/update-password.component.ts @@ -72,10 +72,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent { } async cancel() { - await this.stateService.setOrganizationInvitation(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(["/vault"]); + await this.router.navigate(["/vault"]); } async setupSubmitActions(): Promise { diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html new file mode 100644 index 00000000000..c9c0c296ada --- /dev/null +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.html @@ -0,0 +1,55 @@ +
      + + +

      {{ "creditDelayed" | i18n }}

      +
      + + + {{ "payPal" | i18n }} + + + {{ "bitcoin" | i18n }} + + +
      +
      + + {{ "amount" | i18n }} + + $USD + +
      +
      + + + + +
      +
      +
      + + + + + + + + + + + + + + + +
      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 new file mode 100644 index 00000000000..d3c262c4b7d --- /dev/null +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -0,0 +1,153 @@ +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"; +import { firstValueFrom } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DialogService } from "@bitwarden/components"; + +export type AddAccountCreditDialogParams = { + organizationId?: string; + providerId?: string; +}; + +export enum AddAccountCreditDialogResultType { + Closed = "closed", + Submitted = "submitted", +} + +export const openAddAccountCreditDialog = ( + dialogService: DialogService, + dialogConfig: DialogConfig, +) => + dialogService.open( + AddAccountCreditDialogComponent, + dialogConfig, + ); + +type PayPalConfig = { + businessId?: string; + buttonAction?: string; + returnUrl?: string; + customField?: string; + subject?: string; +}; + +@Component({ + templateUrl: "./add-account-credit-dialog.component.html", +}) +export class AddAccountCreditDialogComponent implements OnInit { + @ViewChild("payPalForm", { read: ElementRef, static: true }) payPalForm: ElementRef; + protected formGroup = new FormGroup({ + paymentMethod: new FormControl(PaymentMethodType.PayPal), + creditAmount: new FormControl(null, [Validators.required, Validators.min(0.01)]), + }); + protected payPalConfig: PayPalConfig; + protected ResultType = AddAccountCreditDialogResultType; + + private organization?: Organization; + private provider?: Provider; + private user?: { id: UserId } & AccountInfo; + + constructor( + private accountService: AccountService, + private apiService: ApiService, + private configService: ConfigService, + @Inject(DIALOG_DATA) private dialogParams: AddAccountCreditDialogParams, + private dialogRef: DialogRef, + private organizationService: OrganizationService, + private platformUtilsService: PlatformUtilsService, + private providerService: ProviderService, + ) { + this.payPalConfig = process.env.PAYPAL_CONFIG as PayPalConfig; + } + + protected readonly paymentMethodType = PaymentMethodType; + + submit = async () => { + this.formGroup.markAllAsTouched(); + + if (this.formGroup.invalid) { + return; + } + + if (this.formGroup.value.paymentMethod === PaymentMethodType.PayPal) { + this.payPalForm.nativeElement.submit(); + return; + } + + if (this.formGroup.value.paymentMethod === PaymentMethodType.BitPay) { + const request = this.getBitPayInvoiceRequest(); + const bitPayUrl = await this.apiService.postBitPayInvoice(request); + this.platformUtilsService.launchUri(bitPayUrl); + return; + } + + this.dialogRef.close(AddAccountCreditDialogResultType.Submitted); + }; + + async ngOnInit(): Promise { + let payPalCustomField: string; + + if (this.dialogParams.organizationId) { + this.formGroup.patchValue({ + creditAmount: 20.0, + }); + this.organization = await this.organizationService.get(this.dialogParams.organizationId); + payPalCustomField = "organization_id:" + this.organization.id; + this.payPalConfig.subject = this.organization.name; + } else if (this.dialogParams.providerId) { + this.formGroup.patchValue({ + creditAmount: 20.0, + }); + this.provider = await this.providerService.get(this.dialogParams.providerId); + payPalCustomField = "provider_id:" + this.provider.id; + this.payPalConfig.subject = this.provider.name; + } else { + this.formGroup.patchValue({ + creditAmount: 10.0, + }); + this.user = await firstValueFrom(this.accountService.activeAccount$); + payPalCustomField = "user_id:" + this.user.id; + this.payPalConfig.subject = this.user.email; + } + + const region = await firstValueFrom(this.configService.cloudRegion$); + + payPalCustomField += ",account_credit:1"; + payPalCustomField += `,region:${region}`; + + this.payPalConfig.customField = payPalCustomField; + this.payPalConfig.returnUrl = window.location.href; + } + + getBitPayInvoiceRequest(): BitPayInvoiceRequest { + const request = new BitPayInvoiceRequest(); + if (this.organization) { + request.name = this.organization.name; + request.organizationId = this.organization.id; + } else if (this.provider) { + request.name = this.provider.name; + request.providerId = this.provider.id; + } else { + request.email = this.user.email; + request.userId = this.user.id; + } + + request.credit = true; + request.amount = this.formGroup.value.creditAmount; + request.returnUrl = window.location.href; + + return request; + } +} diff --git a/libs/angular/src/billing/components/index.ts b/libs/angular/src/billing/components/index.ts new file mode 100644 index 00000000000..748a005df83 --- /dev/null +++ b/libs/angular/src/billing/components/index.ts @@ -0,0 +1,4 @@ +export * from "./add-account-credit-dialog/add-account-credit-dialog.component"; +export * from "./manage-tax-information/manage-tax-information.component"; +export * from "./select-payment-method/select-payment-method.component"; +export * from "./verify-bank-account/verify-bank-account.component"; 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 new file mode 100644 index 00000000000..f9cfa8e0faf --- /dev/null +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.html @@ -0,0 +1,72 @@ +
      +
      +
      + + {{ "country" | i18n }} + + + + +
      +
      + + {{ "zipPostalCode" | i18n }} + + +
      +
      + + + {{ "includeVAT" | i18n }} + +
      +
      +
      +
      + + {{ "taxIdNumber" | i18n }} + + +
      +
      +
      +
      + + {{ "address1" | i18n }} + + +
      +
      + + {{ "address2" | i18n }} + + +
      +
      + + {{ "cityTown" | i18n }} + + +
      +
      + + {{ "stateProvince" | 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 new file mode 100644 index 00000000000..58342548ca3 --- /dev/null +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -0,0 +1,406 @@ +import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; + +import { TaxInformation } from "@bitwarden/common/billing/models/domain"; + +type Country = { + name: string; + value: string; + disabled: boolean; +}; + +@Component({ + selector: "app-manage-tax-information", + templateUrl: "./manage-tax-information.component.html", +}) +export class ManageTaxInformationComponent implements OnInit { + @Input({ required: true }) taxInformation: TaxInformation; + @Input() onSubmit?: (taxInformation: TaxInformation) => Promise; + @Output() taxInformationUpdated = new EventEmitter(); + + protected formGroup = this.formBuilder.group({ + country: ["", Validators.required], + postalCode: ["", Validators.required], + includeTaxId: false, + taxId: "", + line1: "", + line2: "", + city: "", + state: "", + }); + + constructor(private formBuilder: FormBuilder) {} + + submit = async () => { + await this.onSubmit({ + country: this.formGroup.value.country, + postalCode: this.formGroup.value.postalCode, + taxId: this.formGroup.value.taxId, + line1: this.formGroup.value.line1, + line2: this.formGroup.value.line2, + city: this.formGroup.value.city, + state: this.formGroup.value.state, + }); + + this.taxInformationUpdated.emit(); + }; + + async ngOnInit() { + if (this.taxInformation) { + this.formGroup.patchValue({ + ...this.taxInformation, + includeTaxId: + this.countrySupportsTax(this.taxInformation.country) && + (!!this.taxInformation.taxId || + !!this.taxInformation.line1 || + !!this.taxInformation.line2 || + !!this.taxInformation.city || + !!this.taxInformation.state), + }); + } + } + + protected countrySupportsTax(countryCode: string) { + return this.taxSupportedCountryCodes.includes(countryCode); + } + + protected get includeTaxIdIsSelected() { + return this.formGroup.value.includeTaxId; + } + + protected get selectionSupportsAdditionalOptions() { + return ( + this.formGroup.value.country !== "US" && this.countrySupportsTax(this.formGroup.value.country) + ); + } + + 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/billing/components/select-payment-method/select-payment-method.component.html b/libs/angular/src/billing/components/select-payment-method/select-payment-method.component.html new file mode 100644 index 00000000000..7add3f6d35d --- /dev/null +++ b/libs/angular/src/billing/components/select-payment-method/select-payment-method.component.html @@ -0,0 +1,151 @@ +
      +
      + + + + + {{ "creditCard" | i18n }} + + + + + + {{ "bankAccount" | i18n }} + + + + + + {{ "payPal" | i18n }} + + + + + + {{ "accountCredit" | i18n }} + + + +
      + + +
      +
      + +
      +
      +
      + Visa, MasterCard, Discover, AmEx, JCB, Diners Club, UnionPay +
      +
      + +
      +
      +
      +
      + + + + +
      +
      +
      +
      +
      + + + + {{ "verifyBankAccountInitialDesc" | i18n }} {{ "verifyBankAccountFailureWarning" | i18n }} + +
      + + {{ "routingNumber" | i18n }} + + + + {{ "accountNumber" | i18n }} + + + + {{ "accountHolderName" | i18n }} + + + + {{ "bankAccountType" | i18n }} + + + + + + +
      +
      + + +
      +
      + {{ "paypalClickSubmit" | i18n }} +
      +
      + + + + {{ "makeSureEnoughCredit" | i18n }} + + + +
      diff --git a/libs/angular/src/billing/components/select-payment-method/select-payment-method.component.ts b/libs/angular/src/billing/components/select-payment-method/select-payment-method.component.ts new file mode 100644 index 00000000000..4dc39334a70 --- /dev/null +++ b/libs/angular/src/billing/components/select-payment-method/select-payment-method.component.ts @@ -0,0 +1,159 @@ +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { Subject } from "rxjs"; +import { takeUntil } from "rxjs/operators"; + +import { + BillingApiServiceAbstraction, + BraintreeServiceAbstraction, + StripeServiceAbstraction, +} from "@bitwarden/common/billing/abstractions"; +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { TokenizedPaymentMethod } from "@bitwarden/common/billing/models/domain"; + +@Component({ + selector: "app-select-payment-method", + templateUrl: "./select-payment-method.component.html", +}) +export class SelectPaymentMethodComponent implements OnInit, OnDestroy { + @Input() protected showAccountCredit: boolean = true; + @Input() protected showBankAccount: boolean = true; + @Input() protected showPayPal: boolean = true; + @Input() private startWith: PaymentMethodType = PaymentMethodType.Card; + @Input() protected onSubmit: (tokenizedPaymentMethod: TokenizedPaymentMethod) => Promise; + + private destroy$ = new Subject(); + + protected formGroup = this.formBuilder.group({ + paymentMethod: [this.startWith], + bankInformation: this.formBuilder.group({ + routingNumber: ["", [Validators.required]], + accountNumber: ["", [Validators.required]], + accountHolderName: ["", [Validators.required]], + accountHolderType: ["", [Validators.required]], + }), + }); + protected PaymentMethodType = PaymentMethodType; + + constructor( + private billingApiService: BillingApiServiceAbstraction, + private braintreeService: BraintreeServiceAbstraction, + private formBuilder: FormBuilder, + private stripeService: StripeServiceAbstraction, + ) {} + + async tokenizePaymentMethod(): Promise { + const type = this.selected; + + if (this.usingStripe) { + const clientSecret = await this.billingApiService.createSetupIntent(type); + + if (this.usingBankAccount) { + const token = await this.stripeService.setupBankAccountPaymentMethod(clientSecret, { + accountHolderName: this.formGroup.value.bankInformation.accountHolderName, + routingNumber: this.formGroup.value.bankInformation.routingNumber, + accountNumber: this.formGroup.value.bankInformation.accountNumber, + accountHolderType: this.formGroup.value.bankInformation.accountHolderType, + }); + return { + type, + token, + }; + } + + if (this.usingCard) { + const token = await this.stripeService.setupCardPaymentMethod(clientSecret); + return { + type, + token, + }; + } + } + + if (this.usingPayPal) { + const token = await this.braintreeService.requestPaymentMethod(); + return { + type, + token, + }; + } + + return null; + } + + submit = async () => { + const tokenizedPaymentMethod = await this.tokenizePaymentMethod(); + await this.onSubmit(tokenizedPaymentMethod); + }; + + ngOnInit(): void { + this.stripeService.loadStripe( + { + cardNumber: "#stripe-card-number", + cardExpiry: "#stripe-card-expiry", + cardCvc: "#stripe-card-cvc", + }, + this.startWith === PaymentMethodType.Card, + ); + + if (this.showPayPal) { + this.braintreeService.loadBraintree( + "#braintree-container", + this.startWith === PaymentMethodType.PayPal, + ); + } + + this.formGroup + .get("paymentMethod") + .valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((type) => { + this.onPaymentMethodChange(type); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + this.stripeService.unloadStripe(); + if (this.showPayPal) { + this.braintreeService.unloadBraintree(); + } + } + + private onPaymentMethodChange(type: PaymentMethodType): void { + switch (type) { + case PaymentMethodType.Card: { + this.stripeService.mountElements(); + break; + } + case PaymentMethodType.PayPal: { + this.braintreeService.createDropin(); + break; + } + } + } + + private get selected(): PaymentMethodType { + return this.formGroup.value.paymentMethod; + } + + protected get usingAccountCredit(): boolean { + return this.selected === PaymentMethodType.Credit; + } + + protected get usingBankAccount(): boolean { + return this.selected === PaymentMethodType.BankAccount; + } + + protected get usingCard(): boolean { + return this.selected === PaymentMethodType.Card; + } + + protected get usingPayPal(): boolean { + return this.selected === PaymentMethodType.PayPal; + } + + private get usingStripe(): boolean { + return this.usingBankAccount || this.usingCard; + } +} diff --git a/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.html b/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.html new file mode 100644 index 00000000000..f338f5b0817 --- /dev/null +++ b/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.html @@ -0,0 +1,18 @@ + +

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

      +
      + + {{ "amountX" | i18n: "1" }} + + $0. + + + {{ "amountX" | i18n: "2" }} + + $0. + + +
      +
      diff --git a/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.ts b/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.ts new file mode 100644 index 00000000000..c8abb65d819 --- /dev/null +++ b/libs/angular/src/billing/components/verify-bank-account/verify-bank-account.component.ts @@ -0,0 +1,33 @@ +import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { FormBuilder, FormControl, Validators } from "@angular/forms"; + +@Component({ + selector: "app-verify-bank-account", + templateUrl: "./verify-bank-account.component.html", +}) +export class VerifyBankAccountComponent { + @Input() onSubmit?: (amount1: number, amount2: number) => Promise; + @Output() verificationSubmitted = new EventEmitter(); + + protected formGroup = this.formBuilder.group({ + amount1: new FormControl(null, [ + Validators.required, + Validators.min(0), + Validators.max(99), + ]), + amount2: new FormControl(null, [ + Validators.required, + Validators.min(0), + Validators.max(99), + ]), + }); + + constructor(private formBuilder: FormBuilder) {} + + submit = async () => { + if (this.onSubmit) { + await this.onSubmit(this.formGroup.value.amount1, this.formGroup.value.amount2); + } + this.verificationSubmitted.emit(); + }; +} diff --git a/libs/angular/src/billing/images/cards.png b/libs/angular/src/billing/images/cards.png new file mode 100644 index 00000000000..bd43abe54c5 Binary files /dev/null and b/libs/angular/src/billing/images/cards.png differ diff --git a/libs/angular/src/jslib.module.ts b/libs/angular/src/jslib.module.ts index 5f1bf796aa9..ccb7446d863 100644 --- a/libs/angular/src/jslib.module.ts +++ b/libs/angular/src/jslib.module.ts @@ -2,7 +2,24 @@ import { CommonModule, DatePipe } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { AutofocusDirective, ToastModule } from "@bitwarden/components"; +import { + AddAccountCreditDialogComponent, + ManageTaxInformationComponent, + SelectPaymentMethodComponent, + VerifyBankAccountComponent, +} from "@bitwarden/angular/billing/components"; +import { + AsyncActionsModule, + AutofocusDirective, + ButtonModule, + CheckboxModule, + DialogModule, + FormFieldModule, + RadioButtonModule, + SelectModule, + ToastModule, + TypographyModule, +} from "@bitwarden/components"; import { CalloutComponent } from "./components/callout.component"; import { A11yInvalidDirective } from "./directives/a11y-invalid.directive"; @@ -41,6 +58,14 @@ import { IconComponent } from "./vault/components/icon.component"; CommonModule, FormsModule, ReactiveFormsModule, + AsyncActionsModule, + RadioButtonModule, + FormFieldModule, + SelectModule, + ButtonModule, + CheckboxModule, + DialogModule, + TypographyModule, ], declarations: [ A11yInvalidDirective, @@ -70,6 +95,10 @@ import { IconComponent } from "./vault/components/icon.component"; UserTypePipe, IfFeatureDirective, FingerprintPipe, + AddAccountCreditDialogComponent, + ManageTaxInformationComponent, + SelectPaymentMethodComponent, + VerifyBankAccountComponent, ], exports: [ A11yInvalidDirective, @@ -100,6 +129,10 @@ import { IconComponent } from "./vault/components/icon.component"; UserTypePipe, IfFeatureDirective, FingerprintPipe, + AddAccountCreditDialogComponent, + ManageTaxInformationComponent, + SelectPaymentMethodComponent, + VerifyBankAccountComponent, ], providers: [ CreditCardNumberPipe, diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 9a94659e69f..40405b062c6 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,6 +1,7 @@ import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; +import { LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { AbstractStorageService, @@ -36,7 +37,7 @@ export const MEMORY_STORAGE = new SafeInjectionToken("ME export const SECURE_STORAGE = new SafeInjectionToken("SECURE_STORAGE"); export const STATE_FACTORY = new SafeInjectionToken("STATE_FACTORY"); export const LOGOUT_CALLBACK = new SafeInjectionToken< - (expired: boolean, userId?: string) => Promise + (logoutReason: LogoutReason, userId?: string) => Promise >("LOGOUT_CALLBACK"); export const LOCKED_CALLBACK = new SafeInjectionToken<(userId?: string) => Promise>( "LOCKED_CALLBACK", @@ -49,7 +50,11 @@ export const SYSTEM_THEME_OBSERVABLE = new SafeInjectionToken("DEFAULT_VAULT_TIMEOUT"); -export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken>>( - "INTRAPROCESS_MESSAGING_SUBJECT", -); +export const INTRAPROCESS_MESSAGING_SUBJECT = new SafeInjectionToken< + Subject>> +>("INTRAPROCESS_MESSAGING_SUBJECT"); export const CLIENT_TYPE = new SafeInjectionToken("CLIENT_TYPE"); + +export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => void>( + "REFRESH_ACCESS_TOKEN_ERROR_CALLBACK", +); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 1f7b714fc85..8c676bdb9d9 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -13,6 +13,7 @@ import { InternalUserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsService, UserDecryptionOptionsServiceAbstraction, + LogoutReason, } from "@bitwarden/auth/common"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; @@ -109,14 +110,20 @@ import { DomainSettingsService, DefaultDomainSettingsService, } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { + BillingApiServiceAbstraction, + BraintreeServiceAbstraction, + OrganizationBillingServiceAbstraction, + PaymentMethodWarningsServiceAbstraction, + StripeServiceAbstraction, +} from "@bitwarden/common/billing/abstractions"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billilng-api.service.abstraction"; -import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions/organization-billing.service"; -import { PaymentMethodWarningsServiceAbstraction } from "@bitwarden/common/billing/abstractions/payment-method-warnings-service.abstraction"; import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/billing/services/account/billing-account-profile-state.service"; import { BillingApiService } from "@bitwarden/common/billing/services/billing-api.service"; import { OrganizationBillingService } from "@bitwarden/common/billing/services/organization-billing.service"; import { PaymentMethodWarningsService } from "@bitwarden/common/billing/services/payment-method-warnings.service"; +import { BraintreeService } from "@bitwarden/common/billing/services/payment-processors/braintree.service"; +import { StripeService } from "@bitwarden/common/billing/services/payment-processors/stripe.service"; import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; @@ -180,6 +187,9 @@ import { DefaultStateProvider } from "@bitwarden/common/platform/state/implement import { StateEventRegistrarService } from "@bitwarden/common/platform/state/state-event-registrar.service"; import { StateEventRunnerService } from "@bitwarden/common/platform/state/state-event-runner.service"; /* eslint-enable import/no-restricted-paths */ +import { SyncService } from "@bitwarden/common/platform/sync"; +// eslint-disable-next-line no-restricted-imports -- Needed for DI +import { DefaultSyncService } from "@bitwarden/common/platform/sync/internal"; import { DefaultThemeStateService, ThemeStateService, @@ -219,8 +229,6 @@ import { FolderService as FolderServiceAbstraction, InternalFolderService, } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { SyncNotifierService as SyncNotifierServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync-notifier.service.abstraction"; -import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service"; import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; @@ -228,10 +236,9 @@ import { CollectionService } from "@bitwarden/common/vault/services/collection.s import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; -import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; -import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { VaultSettingsService } from "@bitwarden/common/vault/services/vault-settings/vault-settings.service"; +import { ToastService } from "@bitwarden/components"; import { ImportApiService, ImportApiServiceAbstraction, @@ -275,6 +282,7 @@ import { DEFAULT_VAULT_TIMEOUT, INTRAPROCESS_MESSAGING_SUBJECT, CLIENT_TYPE, + REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, } from "./injection-tokens"; import { ModalService } from "./modal.service"; @@ -316,8 +324,12 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: LOGOUT_CALLBACK, useFactory: - (messagingService: MessagingServiceAbstraction) => (expired: boolean, userId?: string) => - Promise.resolve(messagingService.send("logout", { expired: expired, userId: userId })), + (messagingService: MessagingServiceAbstraction) => + async (logoutReason: LogoutReason, userId?: string) => { + return Promise.resolve( + messagingService.send("logout", { logoutReason: logoutReason, userId: userId }), + ); + }, deps: [MessagingServiceAbstraction], }), safeProvider({ @@ -526,6 +538,7 @@ const safeProviders: SafeProvider[] = [ KeyGenerationServiceAbstraction, EncryptService, LogService, + LOGOUT_CALLBACK, ], }), safeProvider({ @@ -579,6 +592,17 @@ const safeProviders: SafeProvider[] = [ StateProvider, ], }), + safeProvider({ + provide: REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, + useFactory: (toastService: ToastService, i18nService: I18nServiceAbstraction) => () => { + toastService.showToast({ + variant: "error", + title: i18nService.t("errorRefreshingAccessToken"), + message: i18nService.t("errorRefreshingAccessTokenDesc"), + }); + }, + deps: [ToastService, I18nServiceAbstraction], + }), safeProvider({ provide: ApiServiceAbstraction, useClass: ApiService, @@ -587,8 +611,10 @@ const safeProviders: SafeProvider[] = [ PlatformUtilsServiceAbstraction, EnvironmentService, AppIdServiceAbstraction, - VaultTimeoutSettingsServiceAbstraction, + REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, + LogService, LOGOUT_CALLBACK, + VaultTimeoutSettingsServiceAbstraction, ], }), safeProvider({ @@ -617,8 +643,8 @@ const safeProviders: SafeProvider[] = [ deps: [ApiServiceAbstraction, FileUploadServiceAbstraction, InternalSendService], }), safeProvider({ - provide: SyncServiceAbstraction, - useClass: SyncService, + provide: SyncService, + useClass: DefaultSyncService, deps: [ InternalMasterPasswordServiceAbstraction, AccountServiceAbstraction, @@ -649,7 +675,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: BroadcasterService, useClass: DefaultBroadcasterService, - deps: [MessageSender, MessageListener], + deps: [MessageListener], }), safeProvider({ provide: VaultTimeoutSettingsServiceAbstraction, @@ -769,7 +795,7 @@ const safeProviders: SafeProvider[] = [ useClass: devFlagEnabled("noopNotifications") ? NoopNotificationsService : NotificationsService, deps: [ LogService, - SyncServiceAbstraction, + SyncService, AppIdServiceAbstraction, ApiServiceAbstraction, EnvironmentService, @@ -915,12 +941,7 @@ const safeProviders: SafeProvider[] = [ // it depends on SyncService so that new data can be retrieved through the sync // rather than updating the OrganizationService directly. Instead OrganizationService // subscribes to sync notifications and will update itself based on that. - deps: [ApiServiceAbstraction, SyncServiceAbstraction], - }), - safeProvider({ - provide: SyncNotifierServiceAbstraction, - useClass: SyncNotifierService, - deps: [], + deps: [ApiServiceAbstraction, SyncService], }), safeProvider({ provide: DefaultConfigService, @@ -1095,7 +1116,7 @@ const safeProviders: SafeProvider[] = [ EncryptService, I18nServiceAbstraction, OrganizationApiServiceAbstraction, - SyncServiceAbstraction, + SyncService, ], }), safeProvider({ @@ -1165,17 +1186,19 @@ const safeProviders: SafeProvider[] = [ }), safeProvider({ provide: INTRAPROCESS_MESSAGING_SUBJECT, - useFactory: () => new Subject>(), + useFactory: () => new Subject>>(), deps: [], }), safeProvider({ provide: MessageListener, - useFactory: (subject: Subject>) => new MessageListener(subject.asObservable()), + useFactory: (subject: Subject>>) => + new MessageListener(subject.asObservable()), deps: [INTRAPROCESS_MESSAGING_SUBJECT], }), safeProvider({ provide: MessageSender, - useFactory: (subject: Subject>) => new SubjectMessageSender(subject), + useFactory: (subject: Subject>>) => + new SubjectMessageSender(subject), deps: [INTRAPROCESS_MESSAGING_SUBJECT], }), safeProvider({ @@ -1188,6 +1211,16 @@ const safeProviders: SafeProvider[] = [ useClass: KdfConfigService, deps: [StateProvider], }), + safeProvider({ + provide: BraintreeServiceAbstraction, + useClass: BraintreeService, + deps: [LogService], + }), + safeProvider({ + provide: StripeServiceAbstraction, + useClass: StripeService, + deps: [LogService], + }), ]; function encryptServiceFactory( 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 26cab308095..6d5fc9b8dad 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 @@ -1,4 +1,10 @@ - + + 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 fb98825b1b6..5efd2cf9ab4 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 @@ -9,6 +9,7 @@ export interface AnonLayoutWrapperData { pageTitle?: string; pageSubtitle?: string; pageIcon?: Icon; + showReadonlyHostname?: boolean; } @Component({ @@ -20,6 +21,7 @@ export class AnonLayoutWrapperComponent { protected pageTitle: string; protected pageSubtitle: string; protected pageIcon: Icon; + protected showReadonlyHostname: boolean; constructor( private route: ActivatedRoute, @@ -27,6 +29,7 @@ export class AnonLayoutWrapperComponent { ) { this.pageTitle = this.i18nService.t(this.route.snapshot.firstChild.data["pageTitle"]); this.pageSubtitle = this.i18nService.t(this.route.snapshot.firstChild.data["pageSubtitle"]); - this.pageIcon = this.route.snapshot.firstChild.data["pageIcon"]; // don't translate + this.pageIcon = this.route.snapshot.firstChild.data["pageIcon"]; + this.showReadonlyHostname = this.route.snapshot.firstChild.data["showReadonlyHostname"]; } } 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 1152d2efbbd..b6eeb70d5d5 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -13,16 +13,24 @@

      {{ subtitle }}

      -
      +
      -
      © {{ year }} Bitwarden Inc.
      -
      {{ version }}
      +
      {{ "accessing" | i18n }} {{ hostname }}
      + + + + +
      © {{ year }} Bitwarden Inc.
      +
      {{ version }}
      +
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 106844fb5aa..6aa644ea7c3 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -1,9 +1,13 @@ import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { firstValueFrom } from "rxjs"; +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"; import { IconModule, Icon } from "../../../../components/src/icon"; +import { SharedModule } from "../../../../components/src/shared"; import { TypographyModule } from "../../../../components/src/typography"; import { BitwardenLogo } from "../icons/bitwarden-logo.icon"; @@ -11,21 +15,34 @@ import { BitwardenLogo } from "../icons/bitwarden-logo.icon"; standalone: true, selector: "auth-anon-layout", templateUrl: "./anon-layout.component.html", - imports: [IconModule, CommonModule, TypographyModule], + imports: [IconModule, CommonModule, TypographyModule, SharedModule], }) export class AnonLayoutComponent { @Input() title: string; @Input() subtitle: string; @Input() icon: Icon; + @Input() showReadonlyHostname: boolean; protected logo = BitwardenLogo; - protected version: string; - protected year = "2024"; - constructor(private platformUtilsService: PlatformUtilsService) {} + protected year = "2024"; + protected clientType: ClientType; + protected hostname: string; + protected version: string; + + protected showYearAndVersion = true; + + constructor( + private environmentService: EnvironmentService, + private platformUtilsService: PlatformUtilsService, + ) { + this.year = new Date().getFullYear().toString(); + this.clientType = this.platformUtilsService.getClientType(); + this.showYearAndVersion = this.clientType === ClientType.Web; + } async ngOnInit() { - this.year = new Date().getFullYear().toString(); + 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.mdx b/libs/auth/src/angular/anon-layout/anon-layout.mdx index b98523bba18..3b1de735382 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.mdx +++ b/libs/auth/src/angular/anon-layout/anon-layout.mdx @@ -44,9 +44,15 @@ The AnonLayout**Wrapper**Component embeds the AnonLayoutComponent along with the ```html - + + ``` @@ -54,53 +60,60 @@ To implement, the developer does not need to work with the base AnonLayoutCompon devoloper simply uses the AnonLayout**Wrapper**Component in `oss-routing.module.ts` (for Web, for example) to construct the page via routable composition: -```javascript +```typescript // File: oss-routing.module.ts +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular"; { - path: "", - component: AnonLayoutWrapperComponent, // Wrapper component - children: [ - { - path: "sample-route", // replace with your route - children: [ - { - path: "", - component: MyPrimaryComponent, // replace with your component - }, - { - path: "", - component: MySecondaryComponent, // replace with your component (or remove this secondary outlet object entirely if not needed) - outlet: "secondary", - }, - ], - data: { - pageTitle: "logIn", // example of a translation key from messages.json - pageSubtitle: "loginWithMasterPassword", // example of a translation key from messages.json - pageIcon: LockIcon, // example of an icon to pass in - } satisfies AnonLayoutWrapperData, - }, - ], - }, + path: "", + component: AnonLayoutWrapperComponent, // Wrapper component + children: [ + { + path: "sample-route", // replace with your route + children: [ + { + path: "", + component: MyPrimaryComponent, // replace with your component + }, + { + path: "", + component: MySecondaryComponent, // replace with your component (or remove this secondary outlet object entirely if not needed) + outlet: "secondary", + }, + ], + data: { + pageTitle: "logIn", // example of a translation key from messages.json + pageSubtitle: "loginWithMasterPassword", // example of a translation key from messages.json + pageIcon: LockIcon, // example of an icon to pass in + } satisfies AnonLayoutWrapperData, + }, + ], +}, ``` -And if the AnonLayout**Wrapper**Component is already being used in your client's routing module, -then your work will be as simple as just adding another child route under the `children` array. +(Notice that you can optionally add an `outlet: "secondary"` if you want to project secondary +content below the primary content). + +If the AnonLayout**Wrapper**Component is already being used in your client's routing module, then +your work will be as simple as just adding another child route under the `children` array. + +
### Data Properties -In the `oss-routing.module.ts` example above, notice the data properties being passed in: +Routes that use the AnonLayou**tWrapper**Component can take several unique data properties defined +in the `AnonLayoutWrapperData` interface: - For the `pageTitle` and `pageSubtitle` - pass in a translation key from `messages.json`. - For the `pageIcon` - import an icon (of type `Icon`) into the router file and use the icon directly. +- `showReadonlyHostname` - set to `true` if you want to show the hostname in the footer (ex: + "Accessing bitwarden.com") -All 3 of these properties are optional. +All of these properties are optional. -```javascript -import { AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular"; - -// ... +```typescript +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular"; { // ... @@ -108,10 +121,45 @@ import { AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular"; pageTitle: "logIn", pageSubtitle: "loginWithMasterPassword", pageIcon: LockIcon, + showReadonlyHostname: true, } satisfies AnonLayoutWrapperData, } ``` +### Environment Selector + +For some routes, you may want to display the environment selector in the footer of the +AnonLayoutComponent. To do so, add the relevant environment selector (Web or Libs version, depending +on your client) as a component with `outlet: "environment-selector"`. + +```javascript +// File: oss-routing.module.ts +import { AnonLayoutWrapperComponent, AnonLayoutWrapperData, LockIcon } from "@bitwarden/auth/angular"; +import { EnvironmentSelectorComponent } from "./components/environment-selector/environment-selector.component"; + +{ + path: "", + component: AnonLayoutWrapperComponent, + children: [ + { + path: "sample-route", + children: [ + { + path: "", + component: MyPrimaryComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, // use Web or Libs component depending on your client + outlet: "environment-selector", + }, + ], + // ... + }, + ], +}, +``` + --- 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 103098349a0..c9054fb5e63 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -1,14 +1,20 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; +import { BehaviorSubject } from "rxjs"; +import { ClientType } from "@bitwarden/common/enums"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ButtonModule } from "../../../../components/src/button"; +import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; import { LockIcon } from "../icons"; import { AnonLayoutComponent } from "./anon-layout.component"; class MockPlatformUtilsService implements Partial { getApplicationVersion = () => Promise.resolve("Version 2024.1.1"); + getClientType = () => ClientType.Web; } export default { @@ -22,12 +28,31 @@ export default { provide: PlatformUtilsService, useClass: MockPlatformUtilsService, }, + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + accessing: "Accessing", + }); + }, + }, + { + provide: EnvironmentService, + useValue: { + environment$: new BehaviorSubject({ + getHostname() { + return "bitwarden.com"; + }, + }).asObservable(), + }, + }, ], }), ], args: { title: "The Page Title", subtitle: "The subtitle (optional)", + showReadonlyHostname: true, icon: LockIcon, }, } as Meta; @@ -40,7 +65,7 @@ export const WithPrimaryContent: Story = { template: // Projected content (the
) and styling is just a sample and can be replaced with any content/styling. ` - +
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?
@@ -57,7 +82,7 @@ export const WithSecondaryContent: Story = { // Projected content (the
's) and styling is just a sample and can be replaced with any content/styling. // Notice that slot="secondary" is requred to project any secondary content. ` - +
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?
@@ -78,7 +103,7 @@ export const WithLongContent: Story = { template: // Projected content (the
's) and styling is just a sample and can be replaced with any content/styling. ` - +
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? Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit. Lorem ipsum dolor sit amet consectetur adipisicing elit.
@@ -94,13 +119,31 @@ export const WithLongContent: Story = { }), }; +export const WithThinPrimaryContent: Story = { + render: (args) => ({ + props: args, + template: + // Projected content (the
's) and styling is just a sample and can be replaced with any content/styling. + ` + +
Lorem ipsum
+ +
+
Secondary Projected Content (optional)
+ +
+
+ `, + }), +}; + export const WithIcon: Story = { render: (args) => ({ props: args, template: // Projected content (the
) and styling is just a sample and can be replaced with any content/styling. ` - +
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/registration/registration-env-selector/registration-env-selector.component.html b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.html index b4dad835eec..9785bf05ab5 100644 --- a/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.html +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-env-selector.component.html @@ -8,6 +8,7 @@ [label]="regionConfig.domain" > 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 f01873dd3e2..fe41f0a3ac7 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,17 +1,26 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms"; -import { EMPTY, Subject, from, map, of, switchMap, takeUntil, tap } from "rxjs"; +import { Subject, from, map, of, pairwise, startWith, switchMap, takeUntil, tap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { ClientType } from "@bitwarden/common/enums"; import { Environment, EnvironmentService, Region, RegionConfig, } from "@bitwarden/common/platform/abstractions/environment.service"; -import { FormFieldModule, SelectModule } from "@bitwarden/components"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService, FormFieldModule, SelectModule, ToastService } from "@bitwarden/components"; +import { RegistrationSelfHostedEnvConfigDialogComponent } from "./registration-self-hosted-env-config-dialog.component"; + +/** + * Component for selecting the environment to register with in the email verification registration flow. + * Outputs the selected region to the parent component so it can respond as necessary. + */ @Component({ standalone: true, selector: "auth-registration-env-selector", @@ -19,7 +28,7 @@ import { FormFieldModule, SelectModule } from "@bitwarden/components"; imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule], }) export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy { - @Output() onOpenSelfHostedSettings = new EventEmitter(); + @Output() selectedRegionChange = new EventEmitter(); ServerEnvironmentType = Region; @@ -33,12 +42,24 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy { availableRegionConfigs: RegionConfig[] = this.environmentService.availableRegions(); + private selectedRegionFromEnv: RegionConfig | Region.SelfHosted; + + isDesktopOrBrowserExtension = false; + private destroy$ = new Subject(); constructor( private formBuilder: FormBuilder, private environmentService: EnvironmentService, - ) {} + private dialogService: DialogService, + private i18nService: I18nService, + private toastService: ToastService, + private platformUtilsService: PlatformUtilsService, + ) { + const clientType = platformUtilsService.getClientType(); + this.isDesktopOrBrowserExtension = + clientType === ClientType.Desktop || clientType === ClientType.Browser; + } async ngOnInit() { await this.initSelectedRegionAndListenForEnvChanges(); @@ -61,13 +82,17 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy { return regionConfig; }), - tap((selectedRegionInitialValue: RegionConfig | Region.SelfHosted) => { - // This inits the form control with the selected region, but - // it also sets the value to self hosted if the self hosted settings are saved successfully - // in the client specific implementation managed by the parent component. - // It also resets the value to the previously selected region if the self hosted - // settings are closed without saving. We don't emit the event to avoid a loop. - this.selectedRegion.setValue(selectedRegionInitialValue, { emitEvent: false }); + tap((selectedRegionFromEnv: RegionConfig | Region.SelfHosted) => { + // Only set the value if it is different from the current value. + if (selectedRegionFromEnv !== this.selectedRegion.value) { + // Don't emit to avoid triggering the selectedRegion valueChanges subscription + // which could loop back to this code. + this.selectedRegion.setValue(selectedRegionFromEnv, { emitEvent: false }); + } + + // Save this off so we can reset the value to the previously selected region + // if the self hosted settings are closed without saving. + this.selectedRegionFromEnv = selectedRegionFromEnv; }), takeUntil(this.destroy$), ) @@ -77,23 +102,66 @@ export class RegistrationEnvSelectorComponent implements OnInit, OnDestroy { private listenForSelectedRegionChanges() { this.selectedRegion.valueChanges .pipe( - switchMap((selectedRegionConfig: RegionConfig | Region.SelfHosted | null) => { - if (selectedRegionConfig === null) { - return of(null); - } + startWith(null), // required so that first user choice is not ignored + pairwise(), + switchMap( + ([prevSelectedRegion, selectedRegion]: [ + RegionConfig | Region.SelfHosted | null, + RegionConfig | Region.SelfHosted | null, + ]) => { + if (selectedRegion === null) { + this.selectedRegionChange.emit(selectedRegion); + return of(null); + } - if (selectedRegionConfig === Region.SelfHosted) { - this.onOpenSelfHostedSettings.emit(); - return EMPTY; - } + if (selectedRegion === Region.SelfHosted) { + return from( + RegistrationSelfHostedEnvConfigDialogComponent.open(this.dialogService), + ).pipe( + tap((result: boolean | undefined) => + this.handleSelfHostedEnvConfigDialogResult(result, prevSelectedRegion), + ), + ); + } - return from(this.environmentService.setEnvironment(selectedRegionConfig.key)); - }), + this.selectedRegionChange.emit(selectedRegion); + return from(this.environmentService.setEnvironment(selectedRegion.key)); + }, + ), takeUntil(this.destroy$), ) .subscribe(); } + private handleSelfHostedEnvConfigDialogResult( + result: boolean | undefined, + prevSelectedRegion: RegionConfig | Region.SelfHosted | null, + ) { + if (result === true) { + this.selectedRegionChange.emit(Region.SelfHosted); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("environmentSaved"), + }); + return; + } + + // Reset the value to the previously selected region or the current env setting + // if the self hosted env settings dialog is closed without saving. + if ( + (result === false || result === undefined) && + prevSelectedRegion !== null && + prevSelectedRegion !== Region.SelfHosted + ) { + this.selectedRegionChange.emit(prevSelectedRegion); + this.selectedRegion.setValue(prevSelectedRegion, { emitEvent: false }); + } else { + this.selectedRegionChange.emit(this.selectedRegionFromEnv); + this.selectedRegion.setValue(this.selectedRegionFromEnv, { emitEvent: false }); + } + } + ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.html b/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.html new file mode 100644 index 00000000000..92c2f9f2f7a --- /dev/null +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.html @@ -0,0 +1,107 @@ +
+ + Self-hosted environment + + + {{ "baseUrl" | i18n }} + + {{ "selfHostedBaseUrlHint" | i18n }} + + + + + +

+ {{ "selfHostedCustomEnvHeader" | i18n }} +

+ + + {{ "webVaultUrl" | i18n }} + + + + + {{ "apiUrl" | i18n }} + + + + + {{ "identityUrl" | i18n }} + + + + + {{ "notificationsUrl" | i18n }} + + + + + {{ "iconsUrl" | i18n }} + + +
+ + + {{ "selfHostedEnvFormInvalid" | i18n }} + +
+ + + + + +
+
diff --git a/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.ts b/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.ts new file mode 100644 index 00000000000..2bedb4b3583 --- /dev/null +++ b/libs/auth/src/angular/registration/registration-env-selector/registration-self-hosted-env-config-dialog.component.ts @@ -0,0 +1,164 @@ +import { DialogRef } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + ReactiveFormsModule, + ValidationErrors, + ValidatorFn, +} from "@angular/forms"; +import { Subject, firstValueFrom } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { + EnvironmentService, + Region, +} from "@bitwarden/common/platform/abstractions/environment.service"; +import { + AsyncActionsModule, + ButtonModule, + DialogModule, + DialogService, + FormFieldModule, + LinkModule, + TypographyModule, +} from "@bitwarden/components"; + +/** + * Validator for self-hosted environment settings form. + * It enforces that at least one URL is provided. + */ +function selfHostedEnvSettingsFormValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const formGroup = control as FormGroup; + const baseUrl = formGroup.get("baseUrl")?.value; + const webVaultUrl = formGroup.get("webVaultUrl")?.value; + const apiUrl = formGroup.get("apiUrl")?.value; + const identityUrl = formGroup.get("identityUrl")?.value; + const iconsUrl = formGroup.get("iconsUrl")?.value; + const notificationsUrl = formGroup.get("notificationsUrl")?.value; + + if (baseUrl || webVaultUrl || apiUrl || identityUrl || iconsUrl || notificationsUrl) { + return null; // valid + } else { + return { atLeastOneUrlIsRequired: true }; // invalid + } + }; +} + +/** + * Dialog for configuring self-hosted environment settings. + */ +@Component({ + standalone: true, + selector: "auth-registration-self-hosted-env-config-dialog", + templateUrl: "registration-self-hosted-env-config-dialog.component.html", + imports: [ + CommonModule, + JslibModule, + DialogModule, + ButtonModule, + LinkModule, + TypographyModule, + ReactiveFormsModule, + FormFieldModule, + AsyncActionsModule, + ], +}) +export class RegistrationSelfHostedEnvConfigDialogComponent implements OnInit, OnDestroy { + /** + * Opens the dialog. + * @param dialogService - Dialog service. + * @returns Promise that resolves to true if the dialog was closed with a successful result, false otherwise. + */ + static async open(dialogService: DialogService): Promise { + const dialogRef = dialogService.open(RegistrationSelfHostedEnvConfigDialogComponent, { + disableClose: false, + }); + + const dialogResult = await firstValueFrom(dialogRef.closed); + + return dialogResult; + } + + formGroup = this.formBuilder.group( + { + baseUrl: [null], + webVaultUrl: [null], + apiUrl: [null], + identityUrl: [null], + iconsUrl: [null], + notificationsUrl: [null], + }, + { validators: selfHostedEnvSettingsFormValidator() }, + ); + + get baseUrl(): FormControl { + return this.formGroup.get("baseUrl") as FormControl; + } + + get webVaultUrl(): FormControl { + return this.formGroup.get("webVaultUrl") as FormControl; + } + + get apiUrl(): FormControl { + return this.formGroup.get("apiUrl") as FormControl; + } + + get identityUrl(): FormControl { + return this.formGroup.get("identityUrl") as FormControl; + } + + get iconsUrl(): FormControl { + return this.formGroup.get("iconsUrl") as FormControl; + } + + get notificationsUrl(): FormControl { + return this.formGroup.get("notificationsUrl") as FormControl; + } + + showCustomEnv = false; + showErrorSummary = false; + + private destroy$ = new Subject(); + + constructor( + private dialogRef: DialogRef, + private formBuilder: FormBuilder, + private environmentService: EnvironmentService, + ) {} + + ngOnInit() {} + + submit = async () => { + this.showErrorSummary = false; + + if (this.formGroup.invalid) { + this.showErrorSummary = true; + return; + } + + await this.environmentService.setEnvironment(Region.SelfHosted, { + base: this.baseUrl.value, + api: this.apiUrl.value, + identity: this.identityUrl.value, + webVault: this.webVaultUrl.value, + icons: this.iconsUrl.value, + notifications: this.notificationsUrl.value, + }); + + this.dialogRef.close(true); + }; + + async cancel() { + this.dialogRef.close(false); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.html b/libs/auth/src/angular/registration/registration-start/registration-start.component.html index 8f64232f9c8..8da2eb76b55 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.html +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.html @@ -1,5 +1,9 @@
+ + {{ "emailAddress" | i18n }} +Note that the self hosted option is not present in the environment selector. -### Self Hosted Example +### US Region - + -### Query Param Example +### EU Region + + + +### Query Params The component accepts two query parameters: `email` and `emailReadonly`. If an email is provided, it will be pre-filled in the email input field. If `emailReadonly` is set to `true`, the email input field will be set to readonly. `emailReadonly` is primarily for the organization invite flow. - + + +## Desktop + +Behavior to note: + +- The self hosted option is present in the environment selector. +- If you go from non-self hosted to self hosted, the terms of service and privacy policy checkbox + will disappear. + +### US Region + + + +### EU Region + + + +### Self Hosted + +Note the fact that the terms of service and privacy policy checkbox is not present when the +environment is self hosted. + + + +## Browser Extension + +Behavior to note: + +- The self hosted option is present in the environment selector. +- If you go from non-self hosted to self hosted, the terms of service and privacy policy checkbox + will disappear. + +### US Region + + + +### EU Region + + + +### Self Hosted + +Note the fact that the terms of service and privacy policy checkbox is not present when the +environment is self hosted. + + 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 099f086b963..50d1f15182e 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 @@ -1,10 +1,30 @@ import { importProvidersFrom } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { ActivatedRoute, Params } from "@angular/router"; import { RouterTestingModule } from "@angular/router/testing"; import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; import { of } from "rxjs"; +import { ClientType } from "@bitwarden/common/enums"; +import { + Environment, + EnvironmentService, + Region, + Urls, +} from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + AsyncActionsModule, + ButtonModule, + DialogModule, + FormFieldModule, + LinkModule, + SelectModule, + ToastOptions, + ToastService, + TypographyModule, +} from "@bitwarden/components"; import { PreloadedEnglishI18nModule } from "../../../../../../apps/web/src/app/core/tests"; @@ -15,52 +35,70 @@ export default { component: RegistrationStartComponent, } as Meta; -const decorators = (options: { isSelfHost: boolean; queryParams: Params }) => { +const decorators = (options: { + isSelfHost?: boolean; + queryParams?: Params; + clientType?: ClientType; + defaultRegion?: Region; +}) => { return [ moduleMetadata({ - imports: [RouterTestingModule], + imports: [ + RouterTestingModule, + DialogModule, + ReactiveFormsModule, + FormFieldModule, + SelectModule, + ButtonModule, + LinkModule, + TypographyModule, + AsyncActionsModule, + BrowserAnimationsModule, + ], providers: [ { provide: ActivatedRoute, - useValue: { queryParams: of(options.queryParams) }, - }, - { - provide: PlatformUtilsService, - useValue: { - isSelfHost: () => options.isSelfHost, - } as Partial, + useValue: { queryParams: of(options.queryParams || {}) }, }, ], }), applicationConfig({ - providers: [importProvidersFrom(PreloadedEnglishI18nModule)], + providers: [ + importProvidersFrom(PreloadedEnglishI18nModule), + { + provide: EnvironmentService, + useValue: { + environment$: of({ + getRegion: () => options.defaultRegion || Region.US, + } as Partial), + availableRegions: () => [ + { key: Region.US, domain: "bitwarden.com", urls: {} }, + { key: Region.EU, domain: "bitwarden.eu", urls: {} }, + ], + setEnvironment: (region: Region, urls?: Urls) => Promise.resolve({}), + } as Partial, + }, + { + provide: PlatformUtilsService, + useValue: { + isSelfHost: () => options.isSelfHost || false, + getClientType: () => options.clientType || ClientType.Web, + } as Partial, + }, + { + provide: ToastService, + useValue: { + showToast: (options: ToastOptions) => {}, + } as Partial, + }, + ], }), ]; }; type Story = StoryObj; -export const CloudExample: Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), - decorators: decorators({ isSelfHost: false, queryParams: {} }), -}; - -export const SelfHostExample: Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), - decorators: decorators({ isSelfHost: true, queryParams: {} }), -}; - -export const QueryParamsExample: Story = { +export const WebUSRegionExample: Story = { render: (args) => ({ props: args, template: ` @@ -68,7 +106,120 @@ export const QueryParamsExample: Story = { `, }), decorators: decorators({ - isSelfHost: false, + clientType: ClientType.Web, + queryParams: {}, + defaultRegion: Region.US, + }), +}; + +export const WebEURegionExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Web, + queryParams: {}, + defaultRegion: Region.EU, + }), +}; + +export const WebUSRegionQueryParamsExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Web, + defaultRegion: Region.US, queryParams: { email: "jaredWasHere@bitwarden.com", emailReadonly: "true" }, }), }; + +export const DesktopUSRegionExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Desktop, + defaultRegion: Region.US, + isSelfHost: false, + }), +}; + +export const DesktopEURegionExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Desktop, + defaultRegion: Region.EU, + isSelfHost: false, + }), +}; + +export const DesktopSelfHostExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Desktop, + isSelfHost: true, + defaultRegion: Region.SelfHosted, + }), +}; + +export const BrowserExtensionUSRegionExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Browser, + defaultRegion: Region.US, + isSelfHost: false, + }), +}; + +export const BrowserExtensionEURegionExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Browser, + defaultRegion: Region.EU, + isSelfHost: false, + }), +}; + +export const BrowserExtensionSelfHostExample: Story = { + render: (args) => ({ + props: args, + template: ` + + `, + }), + decorators: decorators({ + clientType: ClientType.Browser, + isSelfHost: true, + defaultRegion: Region.SelfHosted, + }), +}; 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 8cb40d94524..8c62136d63c 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 @@ -197,8 +197,8 @@ export class UserVerificationFormInputComponent implements ControlValueAccessor, } } - // Don't bother executing secret changes if biometrics verification is active. - if (this.activeClientVerificationOption === ActiveClientVerificationOption.Biometrics) { + // Executing secret changes for all non biometrics verification. Biometrics doesn't have a user entered secret. + if (this.activeClientVerificationOption !== ActiveClientVerificationOption.Biometrics) { this.processSecretChanges(this.secret.value); } diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index eae6dc2a275..a46636532bf 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -3,7 +3,6 @@ import { Observable } from "rxjs"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { MasterKey } from "@bitwarden/common/types/key"; import { @@ -72,12 +71,4 @@ export abstract class LoginStrategyServiceAbstraction { * Creates a master key from the provided master password and email. */ makePreloginKey: (masterPassword: string, email: string) => Promise; - /** - * Sends a response to an auth request. - */ - passwordlessLogin: ( - id: string, - key: string, - requestApproved: boolean, - ) => Promise; } diff --git a/libs/auth/src/common/index.ts b/libs/auth/src/common/index.ts index 936666e1a81..43efd7c6387 100644 --- a/libs/auth/src/common/index.ts +++ b/libs/auth/src/common/index.ts @@ -3,5 +3,6 @@ */ export * from "./abstractions"; export * from "./models"; +export * from "./types"; export * from "./services"; export * from "./utilities"; 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 f425bc697c5..7169fd69e93 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 @@ -24,8 +24,6 @@ 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 { PasswordlessAuthRequest } from "@bitwarden/common/auth/models/request/passwordless-auth.request"; -import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { PreloginRequest } from "@bitwarden/common/models/request/prelogin.request"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -39,7 +37,6 @@ 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 { KdfType } from "@bitwarden/common/platform/enums/kdf-type.enum"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -263,47 +260,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { return await this.cryptoService.makeMasterKey(masterPassword, email, kdfConfig); } - // TODO: move to auth request service - async passwordlessLogin( - id: string, - key: string, - requestApproved: boolean, - ): Promise { - const pubKey = Utils.fromB64ToArray(key); - - const userId = (await firstValueFrom(this.accountService.activeAccount$)).id; - const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); - let keyToEncrypt; - let encryptedMasterKeyHash = null; - - if (masterKey) { - keyToEncrypt = masterKey.encKey; - - // Only encrypt the master password hash if masterKey exists as - // we won't have a masterKeyHash without a masterKey - const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId)); - if (masterKeyHash != null) { - encryptedMasterKeyHash = await this.cryptoService.rsaEncrypt( - Utils.fromUtf8ToArray(masterKeyHash), - pubKey, - ); - } - } else { - const userKey = await this.cryptoService.getUserKey(); - keyToEncrypt = userKey.key; - } - - const encryptedKey = await this.cryptoService.rsaEncrypt(keyToEncrypt, pubKey); - - const request = new PasswordlessAuthRequest( - encryptedKey.encryptedString, - encryptedMasterKeyHash?.encryptedString, - await this.appIdService.getAppId(), - requestApproved, - ); - return await this.apiService.putAuthRequest(id, request); - } - private async clearCache(): Promise { await this.currentAuthnTypeState.update((_) => null); await this.loginStrategyCacheState.update((_) => null); diff --git a/libs/auth/src/common/types/index.ts b/libs/auth/src/common/types/index.ts new file mode 100644 index 00000000000..37ec426fb68 --- /dev/null +++ b/libs/auth/src/common/types/index.ts @@ -0,0 +1 @@ +export * from "./logout-reason.type"; diff --git a/libs/auth/src/common/types/logout-reason.type.ts b/libs/auth/src/common/types/logout-reason.type.ts new file mode 100644 index 00000000000..71fff51064a --- /dev/null +++ b/libs/auth/src/common/types/logout-reason.type.ts @@ -0,0 +1,10 @@ +export type LogoutReason = + | "invalidGrantError" + | "vaultTimeout" + | "invalidSecurityStamp" + | "logoutNotification" + | "keyConnectorError" + | "sessionExpired" + | "accessTokenUnableToBeDecrypted" + | "refreshTokenSecureStorageRetrievalFailure" + | "accountDeleted"; diff --git a/libs/common/spec/fake-state-provider.ts b/libs/common/spec/fake-state-provider.ts index 2078fe3abde..cd868931f20 100644 --- a/libs/common/spec/fake-state-provider.ts +++ b/libs/common/spec/fake-state-provider.ts @@ -15,8 +15,6 @@ import { DerivedStateProvider, UserKeyDefinition, } from "../src/platform/state"; -// eslint-disable-next-line import/no-restricted-paths -- Needed to type check similarly to the real state providers -import { isUserKeyDefinition } from "../src/platform/state/user-key-definition"; import { UserId } from "../src/types/guid"; import { DerivedStateDependencies } from "../src/types/state"; @@ -71,37 +69,28 @@ export class FakeSingleUserStateProvider implements SingleUserStateProvider { mock = mock(); establishedMocks: Map> = new Map(); states: Map> = new Map(); - get( - userId: UserId, - keyDefinition: KeyDefinition | UserKeyDefinition, - ): SingleUserState { - this.mock.get(userId, keyDefinition); - if (keyDefinition instanceof KeyDefinition) { - keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition); - } - const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}_${userId}`; + get(userId: UserId, userKeyDefinition: UserKeyDefinition): SingleUserState { + this.mock.get(userId, userKeyDefinition); + const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}_${userId}`; let result = this.states.get(cacheKey); if (result == null) { let fake: FakeSingleUserState; // Look for established mock - if (this.establishedMocks.has(keyDefinition.key)) { - fake = this.establishedMocks.get(keyDefinition.key) as FakeSingleUserState; + if (this.establishedMocks.has(userKeyDefinition.key)) { + fake = this.establishedMocks.get(userKeyDefinition.key) as FakeSingleUserState; } else { fake = new FakeSingleUserState(userId); } - fake.keyDefinition = keyDefinition; + fake.keyDefinition = userKeyDefinition; result = fake; this.states.set(cacheKey, result); } return result as SingleUserState; } - getFake( - userId: UserId, - keyDefinition: KeyDefinition | UserKeyDefinition, - ): FakeSingleUserState { - return this.get(userId, keyDefinition) as FakeSingleUserState; + getFake(userId: UserId, userKeyDefinition: UserKeyDefinition): FakeSingleUserState { + return this.get(userId, userKeyDefinition) as FakeSingleUserState; } mockFor(userId: UserId, keyDefinitionKey: string, initialValue?: T): FakeSingleUserState { @@ -122,28 +111,25 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider { this.activeUserId$ = accountService.activeAccountSubject.asObservable().pipe(map((a) => a?.id)); } - get(keyDefinition: KeyDefinition | UserKeyDefinition): ActiveUserState { - if (keyDefinition instanceof KeyDefinition) { - keyDefinition = UserKeyDefinition.fromBaseKeyDefinition(keyDefinition); - } - const cacheKey = `${keyDefinition.fullName}_${keyDefinition.stateDefinition.defaultStorageLocation}`; + get(userKeyDefinition: UserKeyDefinition): ActiveUserState { + const cacheKey = `${userKeyDefinition.fullName}_${userKeyDefinition.stateDefinition.defaultStorageLocation}`; let result = this.states.get(cacheKey); if (result == null) { // Look for established mock - if (this.establishedMocks.has(keyDefinition.key)) { - result = this.establishedMocks.get(keyDefinition.key); + if (this.establishedMocks.has(userKeyDefinition.key)) { + result = this.establishedMocks.get(userKeyDefinition.key); } else { result = new FakeActiveUserState(this.accountService); } - result.keyDefinition = keyDefinition; + result.keyDefinition = userKeyDefinition; this.states.set(cacheKey, result); } return result as ActiveUserState; } - getFake(keyDefinition: KeyDefinition | UserKeyDefinition): FakeActiveUserState { - return this.get(keyDefinition) as FakeActiveUserState; + getFake(userKeyDefinition: UserKeyDefinition): FakeActiveUserState { + return this.get(userKeyDefinition) as FakeActiveUserState; } mockFor(keyDefinitionKey: string, initialValue?: T): FakeActiveUserState { @@ -159,70 +145,56 @@ export class FakeActiveUserStateProvider implements ActiveUserStateProvider { export class FakeStateProvider implements StateProvider { mock = mock(); - getUserState$( - keyDefinition: KeyDefinition | UserKeyDefinition, - userId?: UserId, - ): Observable { - if (isUserKeyDefinition(keyDefinition)) { - this.mock.getUserState$(keyDefinition, userId); - } else { - this.mock.getUserState$(keyDefinition, userId); - } + getUserState$(userKeyDefinition: UserKeyDefinition, userId?: UserId): Observable { + this.mock.getUserState$(userKeyDefinition, userId); if (userId) { - return this.getUser(userId, keyDefinition).state$; + return this.getUser(userId, userKeyDefinition).state$; } - return this.getActive(keyDefinition).state$; + return this.getActive(userKeyDefinition).state$; } getUserStateOrDefault$( - keyDefinition: KeyDefinition | UserKeyDefinition, + userKeyDefinition: UserKeyDefinition, config: { userId: UserId | undefined; defaultValue?: T }, ): Observable { const { userId, defaultValue = null } = config; - if (isUserKeyDefinition(keyDefinition)) { - this.mock.getUserStateOrDefault$(keyDefinition, config); - } else { - this.mock.getUserStateOrDefault$(keyDefinition, config); - } + this.mock.getUserStateOrDefault$(userKeyDefinition, config); if (userId) { - return this.getUser(userId, keyDefinition).state$; + return this.getUser(userId, userKeyDefinition).state$; } return this.activeUserId$.pipe( take(1), switchMap((userId) => - userId != null ? this.getUser(userId, keyDefinition).state$ : of(defaultValue), + userId != null ? this.getUser(userId, userKeyDefinition).state$ : of(defaultValue), ), ); } async setUserState( - keyDefinition: KeyDefinition | UserKeyDefinition, + userKeyDefinition: UserKeyDefinition, value: T, userId?: UserId, ): Promise<[UserId, T]> { - await this.mock.setUserState(keyDefinition, value, userId); + await this.mock.setUserState(userKeyDefinition, value, userId); if (userId) { - return [userId, await this.getUser(userId, keyDefinition).update(() => value)]; + return [userId, await this.getUser(userId, userKeyDefinition).update(() => value)]; } else { - return await this.getActive(keyDefinition).update(() => value); + return await this.getActive(userKeyDefinition).update(() => value); } } - getActive(keyDefinition: KeyDefinition | UserKeyDefinition): ActiveUserState { - return this.activeUser.get(keyDefinition); + getActive(userKeyDefinition: UserKeyDefinition): ActiveUserState { + return this.activeUser.get(userKeyDefinition); } getGlobal(keyDefinition: KeyDefinition): GlobalState { return this.global.get(keyDefinition); } - getUser( - userId: UserId, - keyDefinition: KeyDefinition | UserKeyDefinition, - ): SingleUserState { - return this.singleUser.get(userId, keyDefinition); + getUser(userId: UserId, userKeyDefinition: UserKeyDefinition): SingleUserState { + return this.singleUser.get(userId, userKeyDefinition); } getDerived( diff --git a/libs/common/spec/observable-tracker.ts b/libs/common/spec/observable-tracker.ts index 16fad869c3b..9bf0475bee2 100644 --- a/libs/common/spec/observable-tracker.ts +++ b/libs/common/spec/observable-tracker.ts @@ -1,10 +1,11 @@ -import { Observable, Subscription, firstValueFrom, throwError, timeout } from "rxjs"; +import { Observable, Subject, Subscription, firstValueFrom, throwError, timeout } from "rxjs"; /** Test class to enable async awaiting of observable emissions */ export class ObservableTracker { private subscription: Subscription; + private emissionReceived = new Subject(); emissions: T[] = []; - constructor(private observable: Observable) { + constructor(observable: Observable) { this.emissions = this.trackEmissions(observable); } @@ -21,7 +22,7 @@ export class ObservableTracker { */ async expectEmission(msTimeout = 50): Promise { return await firstValueFrom( - this.observable.pipe( + this.emissionReceived.pipe( timeout({ first: msTimeout, with: () => throwError(() => new Error("Timeout exceeded waiting for another emission.")), @@ -34,40 +35,38 @@ export class ObservableTracker { * @param count The number of emissions to wait for */ async pauseUntilReceived(count: number, msTimeout = 50): Promise { - for (let i = 0; i < count - this.emissions.length; i++) { + while (this.emissions.length < count) { await this.expectEmission(msTimeout); } return this.emissions; } - private trackEmissions(observable: Observable): T[] { + private trackEmissions(observable: Observable): T[] { const emissions: T[] = []; this.subscription = observable.subscribe((value) => { - switch (value) { - case undefined: - case null: - emissions.push(value); - return; - default: - // process by type - break; + if (value == null) { + this.emissionReceived.next(null); + return; } switch (typeof value) { case "string": case "number": case "boolean": - emissions.push(value); + this.emissionReceived.next(value); break; case "symbol": // Cheating types to make symbols work at all - emissions.push(value.toString() as T); + this.emissionReceived.next(value as T); break; default: { - emissions.push(clone(value)); + this.emissionReceived.next(clone(value)); } } }); + this.emissionReceived.subscribe((value) => { + emissions.push(value); + }); return emissions; } } diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 73e4f74e63f..ed43849d62b 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -103,6 +103,7 @@ import { EventResponse } from "../models/response/event.response"; import { ListResponse } from "../models/response/list.response"; import { ProfileResponse } from "../models/response/profile.response"; import { UserKeyResponse } from "../models/response/user-key.response"; +import { SyncResponse } from "../platform/sync"; import { UserId } from "../types/guid"; import { AttachmentRequest } from "../vault/models/request/attachment.request"; import { CipherBulkDeleteRequest } from "../vault/models/request/cipher-bulk-delete.request"; @@ -124,7 +125,6 @@ import { CollectionResponse, } from "../vault/models/response/collection.response"; import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; -import { SyncResponse } from "../vault/models/response/sync.response"; /** * @deprecated The `ApiService` class is deprecated and calls should be extracted into individual 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 1e941fd6544..c601aad0607 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,6 +1,7 @@ import { ListResponse } from "../../../models/response/list.response"; import { PolicyType } from "../../enums"; import { MasterPasswordPolicyOptions } from "../../models/domain/master-password-policy-options"; +import { Policy } from "../../models/domain/policy"; import { PolicyRequest } from "../../models/request/policy.request"; import { PolicyResponse } from "../../models/response/policy.response"; @@ -13,7 +14,7 @@ export class PolicyApiServiceAbstraction { token: string, email: string, organizationUserId: string, - ) => Promise>; + ) => Promise; getMasterPasswordPolicyOptsForOrgUser: (orgId: string) => Promise; putPolicy: (organizationId: string, type: PolicyType, request: PolicyRequest) => Promise; 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 eb65303bce9..18680483906 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 @@ -39,6 +39,7 @@ describe("ORGANIZATIONS state", () => { permissions: undefined, resetPasswordEnrolled: false, userId: "userId", + organizationUserId: "organizationUserId", hasPublicAndPrivateKeys: false, providerId: "providerId", providerName: "providerName", 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 02fe0d6bf26..afc6b40b934 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -36,6 +36,7 @@ export class OrganizationData { permissions: PermissionsApi; resetPasswordEnrolled: boolean; userId: string; + organizationUserId: string; hasPublicAndPrivateKeys: boolean; providerId: string; providerName: string; @@ -96,6 +97,7 @@ export class OrganizationData { this.permissions = response.permissions; this.resetPasswordEnrolled = response.resetPasswordEnrolled; this.userId = response.userId; + this.organizationUserId = response.organizationUserId; this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; this.providerId = response.providerId; this.providerName = response.providerName; 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 35846f20726..54185c84da9 100644 --- a/libs/common/src/admin-console/models/data/policy.data.ts +++ b/libs/common/src/admin-console/models/data/policy.data.ts @@ -1,5 +1,6 @@ import { PolicyId } from "../../../types/guid"; import { PolicyType } from "../../enums"; +import { Policy } from "../domain/policy"; import { PolicyResponse } from "../response/policy.response"; export class PolicyData { @@ -20,4 +21,8 @@ export class PolicyData { this.data = response.data; this.enabled = response.enabled; } + + static fromPolicy(policy: Policy): PolicyData { + return Object.assign(new PolicyData(), policy); + } } diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index a4cadcd3928..b68de7ac08b 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -43,6 +43,7 @@ export class Organization { permissions: PermissionsApi; resetPasswordEnrolled: boolean; userId: string; + organizationUserId: string; hasPublicAndPrivateKeys: boolean; providerId: string; providerName: string; @@ -113,6 +114,7 @@ export class Organization { this.permissions = obj.permissions; this.resetPasswordEnrolled = obj.resetPasswordEnrolled; this.userId = obj.userId; + this.organizationUserId = obj.organizationUserId; this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys; this.providerId = obj.providerId; this.providerName = obj.providerName; 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 9092d48c774..1649bf47ba6 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 @@ -36,6 +36,7 @@ export class ProfileOrganizationResponse extends BaseResponse { permissions: PermissionsApi; resetPasswordEnrolled: boolean; userId: string; + organizationUserId: string; providerId: string; providerName: string; providerType?: ProviderType; @@ -86,6 +87,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.permissions = new PermissionsApi(this.getResponseProperty("permissions")); this.resetPasswordEnrolled = this.getResponseProperty("ResetPasswordEnrolled"); this.userId = this.getResponseProperty("UserId"); + this.organizationUserId = this.getResponseProperty("OrganizationUserId"); this.providerId = this.getResponseProperty("ProviderId"); this.providerName = this.getResponseProperty("ProviderName"); this.providerType = this.getResponseProperty("ProviderType"); diff --git a/libs/common/src/admin-console/services/policy/policy-api.service.ts b/libs/common/src/admin-console/services/policy/policy-api.service.ts index c7f093286e1..086bbea1d21 100644 --- a/libs/common/src/admin-console/services/policy/policy-api.service.ts +++ b/libs/common/src/admin-console/services/policy/policy-api.service.ts @@ -47,7 +47,7 @@ export class PolicyApiService implements PolicyApiServiceAbstraction { token: string, email: string, organizationUserId: string, - ): Promise> { + ): Promise { const r = await this.apiService.send( "GET", "/organizations/" + @@ -63,7 +63,7 @@ export class PolicyApiService implements PolicyApiServiceAbstraction { false, true, ); - return new ListResponse(r, PolicyResponse); + return Policy.fromListResponse(new ListResponse(r, PolicyResponse)); } private async getMasterPasswordPolicyResponseForOrgUser( diff --git a/libs/common/src/auth/abstractions/password-reset-enrollment.service.abstraction.ts b/libs/common/src/auth/abstractions/password-reset-enrollment.service.abstraction.ts index 40892785673..7785798f5cd 100644 --- a/libs/common/src/auth/abstractions/password-reset-enrollment.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/password-reset-enrollment.service.abstraction.ts @@ -3,11 +3,15 @@ import { UserKey } from "../../types/key"; export abstract class PasswordResetEnrollmentServiceAbstraction { /* * Checks the user's enrollment status and enrolls them if required + * NOTE: Will also enroll the user in the organization if in the + * invited status */ abstract enrollIfRequired(organizationSsoIdentifier: string): Promise; /** * Enroll current user in password reset + * NOTE: Will also enroll the user in the organization if in the + * invited status * @param organizationId - Organization in which to enroll the user * @returns Promise that resolves when the user is enrolled * @throws Error if the action fails @@ -16,6 +20,8 @@ export abstract class PasswordResetEnrollmentServiceAbstraction { /** * Enroll user in password reset + * NOTE: Will also enroll the user in the organization if in the + * invited status * @param organizationId - Organization in which to enroll the user * @param userId - User to enroll * @param userKey - User's symmetric key diff --git a/libs/common/src/auth/abstractions/token.service.ts b/libs/common/src/auth/abstractions/token.service.ts index d078051f642..a88dfbb278f 100644 --- a/libs/common/src/auth/abstractions/token.service.ts +++ b/libs/common/src/auth/abstractions/token.service.ts @@ -70,16 +70,16 @@ export abstract class TokenService { /** * Gets the access token * @param userId - The optional user id to get the access token for; if not provided, the active user is used. - * @returns A promise that resolves with the access token or undefined. + * @returns A promise that resolves with the access token or null. */ - getAccessToken: (userId?: UserId) => Promise; + getAccessToken: (userId?: UserId) => Promise; /** * Gets the refresh token. * @param userId - The optional user id to get the refresh token for; if not provided, the active user is used. - * @returns A promise that resolves with the refresh token or undefined. + * @returns A promise that resolves with the refresh token or null. */ - getRefreshToken: (userId?: UserId) => Promise; + getRefreshToken: (userId?: UserId) => Promise; /** * Sets the API Key Client ID for the active user id in memory or disk based on the given vaultTimeoutAction and vaultTimeout. diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index a4529084a2a..25e7b92edf2 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,8 +11,8 @@ import { import { ApiService } from "../../abstractions/api.service"; import { CryptoService } from "../../platform/abstractions/crypto.service"; -import { MessagingService } from "../../platform/abstractions/messaging.service"; import { StateService } from "../../platform/abstractions/state.service"; +import { MessageSender } from "../../platform/messaging"; import { Utils } from "../../platform/misc/utils"; import { UserId } from "../../types/guid"; import { AccountService } from "../abstractions/account.service"; @@ -26,7 +26,7 @@ export class AuthService implements AuthServiceAbstraction { constructor( protected accountService: AccountService, - protected messagingService: MessagingService, + protected messageSender: MessageSender, protected cryptoService: CryptoService, protected apiService: ApiService, protected stateService: StateService, @@ -95,6 +95,6 @@ export class AuthService implements AuthServiceAbstraction { logOut(callback: () => void) { callback(); - this.messagingService.send("loggedOut"); + this.messageSender.send("loggedOut"); } } 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 dd98ce2b446..242a7480958 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -175,7 +175,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { } // At this point of rotating their keys, they should still have their old user key in state - const oldUserKey = await firstValueFrom(this.cryptoService.activeUserKey$); + const oldUserKey = await firstValueFrom(this.cryptoService.userKey$(userId)); const deviceIdentifier = await this.appIdService.getAppId(); const secretVerificationRequest = new SecretVerificationRequest(); 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 f61bce563f3..1527870cb49 100644 --- a/libs/common/src/auth/services/device-trust.service.spec.ts +++ b/libs/common/src/auth/services/device-trust.service.spec.ts @@ -595,7 +595,7 @@ describe("deviceTrustService", () => { const fakeNewUserKeyData = new Uint8Array(64); fakeNewUserKeyData.fill(FakeNewUserKeyMarker, 0, 1); fakeNewUserKey = new SymmetricCryptoKey(fakeNewUserKeyData) as UserKey; - cryptoService.activeUserKey$ = of(fakeNewUserKey); + cryptoService.userKey$.mockReturnValue(of(fakeNewUserKey)); }); it("throws an error when a null user id is passed in", async () => { @@ -631,7 +631,9 @@ describe("deviceTrustService", () => { fakeOldUserKeyData.fill(FakeOldUserKeyMarker, 0, 1); // Mock the retrieval of a user key that differs from the new one passed into the method - cryptoService.activeUserKey$ = of(new SymmetricCryptoKey(fakeOldUserKeyData) as UserKey); + cryptoService.userKey$.mockReturnValue( + of(new SymmetricCryptoKey(fakeOldUserKeyData) as UserKey), + ); appIdService.getAppId.mockResolvedValue("test_device_identifier"); diff --git a/libs/common/src/auth/services/key-connector.service.ts b/libs/common/src/auth/services/key-connector.service.ts index 65d1030bd3a..6b81844afb4 100644 --- a/libs/common/src/auth/services/key-connector.service.ts +++ b/libs/common/src/auth/services/key-connector.service.ts @@ -1,5 +1,7 @@ import { firstValueFrom } from "rxjs"; +import { LogoutReason } from "@bitwarden/auth/common"; + import { ApiService } from "../../abstractions/api.service"; import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; import { OrganizationUserType } from "../../admin-console/enums"; @@ -57,7 +59,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { private logService: LogService, private organizationService: OrganizationService, private keyGenerationService: KeyGenerationService, - private logoutCallback: (expired: boolean, userId?: string) => Promise, + private logoutCallback: (logoutReason: LogoutReason, userId?: string) => Promise, private stateProvider: StateProvider, ) { this.usesKeyConnectorState = this.stateProvider.getActive(USES_KEY_CONNECTOR); @@ -192,7 +194,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { if (this.logoutCallback != 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.logoutCallback(false); + this.logoutCallback("keyConnectorError"); } throw new Error("Key Connector error"); } diff --git a/libs/common/src/auth/services/token.service.spec.ts b/libs/common/src/auth/services/token.service.spec.ts index 9c5dd9fc91f..d7a4c527162 100644 --- a/libs/common/src/auth/services/token.service.spec.ts +++ b/libs/common/src/auth/services/token.service.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { firstValueFrom } from "rxjs"; +import { LogoutReason } from "@bitwarden/auth/common"; + import { FakeSingleUserStateProvider, FakeGlobalStateProvider } from "../../../spec"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; @@ -9,11 +11,18 @@ import { LogService } from "../../platform/abstractions/log.service"; import { AbstractStorageService } from "../../platform/abstractions/storage.service"; import { StorageLocation } from "../../platform/enums"; import { StorageOptions } from "../../platform/models/domain/storage-options"; +import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "../../types/csprng"; import { UserId } from "../../types/guid"; import { VaultTimeout, VaultTimeoutStringType } from "../../types/vault-timeout.type"; import { ACCOUNT_ACTIVE_ACCOUNT_ID } from "./account.service"; -import { DecodedAccessToken, TokenService, TokenStorageLocation } from "./token.service"; +import { + AccessTokenKey, + DecodedAccessToken, + TokenService, + TokenStorageLocation, +} from "./token.service"; import { ACCESS_TOKEN_DISK, ACCESS_TOKEN_MEMORY, @@ -36,6 +45,7 @@ describe("TokenService", () => { let keyGenerationService: MockProxy; let encryptService: MockProxy; let logService: MockProxy; + let logoutCallback: jest.Mock, [logoutReason: LogoutReason, userId?: string]>; const memoryVaultTimeoutAction = VaultTimeoutAction.LogOut; const memoryVaultTimeout: VaultTimeout = 30; @@ -46,6 +56,9 @@ describe("TokenService", () => { const accessTokenJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0IiwibmJmIjoxNzA5MzI0MTExLCJpYXQiOjE3MDkzMjQxMTEsImV4cCI6MTcwOTMyNzcxMSwic2NvcGUiOlsiYXBpIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbIkFwcGxpY2F0aW9uIl0sImNsaWVudF9pZCI6IndlYiIsInN1YiI6ImVjZTcwYTEzLTcyMTYtNDNjNC05OTc3LWIxMDMwMTQ2ZTFlNyIsImF1dGhfdGltZSI6MTcwOTMyNDEwNCwiaWRwIjoiYml0d2FyZGVuIiwicHJlbWl1bSI6ZmFsc2UsImVtYWlsIjoiZXhhbXBsZUBiaXR3YXJkZW4uY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJzc3RhbXAiOiJHWTdKQU82NENLS1RLQkI2WkVBVVlMMldPUVU3QVNUMiIsIm5hbWUiOiJUZXN0IFVzZXIiLCJvcmdvd25lciI6WyI5MmI0OTkwOC1iNTE0LTQ1YTgtYmFkYi1iMTAzMDE0OGZlNTMiLCIzOGVkZTMyMi1iNGI0LTRiZDgtOWUwOS1iMTA3MDExMmRjMTEiLCJiMmQwNzAyOC1hNTgzLTRjM2UtOGQ2MC1iMTA3MDExOThjMjkiLCJiZjkzNGJhMi0wZmQ0LTQ5ZjItYTk1ZS1iMTA3MDExZmM5ZTYiLCJjMGI3Zjc1ZC0wMTVmLTQyYzktYjNhNi1iMTA4MDE3NjA3Y2EiXSwiZGV2aWNlIjoiNGI4NzIzNjctMGRhNi00MWEwLWFkY2ItNzdmMmZlZWZjNGY0IiwianRpIjoiNzUxNjFCRTQxMzFGRjVBMkRFNTExQjhDNEUyRkY4OUEifQ.n7roP8sSbfwcYdvRxZNZds27IK32TW6anorE6BORx_Q"; + const encryptedAccessToken = + "2.rFNYSTJoljn8h6GOSNVYdQ==|4dIp7ONJzC+Kx1ClA+1aIAb7EqCQ4OjnADCYdCPg7BKkdheG+yM62ZiONFk+S6at84M+RnGWWO04aIjinTdJhlhyUmszePNATxIfX60Y+bFKQhlMuCtZpYdEmQDzXVgT43YRbf/6NnN9WzhefLqeMiocwoIJTEpLptb+Zcm7T3MJpkX4dR9w5LUOxUTNFEGd5PlWaI8FBavOkNsrzY5skRK70pvFABET5IDeRlKhi8NwbzvTzkO3SisLRzih+djiz5nEZf0+ujeGAp6P+o7l0mB0sXVsNJzcuE4S9QtHLnx31N6z3mQm5pOgP4EmEOdRIcQGc1p7dL1vXcXtaTJLtfKXoJjJbYT3wplnY9Pf8+2FVxdbM3bRB2yVsnEzgLcf9UchKThQSdOy8+5TO/prDbUt5mDpO4GmRltom5ncda8yJaD3Hw1DO7fa0Xh+kfeByxb1AwBC+GTPfqmo5uqr0J4dZsf9cGlPMTElwR3GYmD60OcQ6iDX36CZZjqqJqBwKSpepDXV39p9G347e6YAAvJenLDKtdjgfWXCMXbkwETbMgYooFDRd60KYsGIXV16UwzJSvczgTY2d+hYb2Cl0lClequaiwcRxLVtW2xau6qoEPjTqJjJi9I0Cs2WNL4LRH96Ir14a3bEtnTvkO1NjN+bQNon+KksaP2BqTbuiAfZbBP/cL4S1Oew4G00PSLZUGV5S1BI0ooJy6e2NLQJlYqfCeKM6RgpvgfOiXlZddVgkkB6lohLjyVvcSZNuKPjs1wZMZ9C76bKb6o39NFK8G3/YScELFf9gkueWjmhcjrs22+xNDn5rxXeedwIkVW9UJVNLc//eGxLfp70y8fNDcyTPRN1UUpqT8+wSz+9ZHl4DLUK0DE2jIveEDke8vi4MK/XLMC/c50rr1NCEuVy6iA3nwiOzVo/GNfeKTpzMcR/D9A0gxkC9GyZ3riSsMQsGNXhZCZLdsFYp0gLiiJxVilMUfyTWaygsNm87GPY3ep3GEHcq/pCuxrpLQQYT3V1j95WJvFxb8dSLiPHb8STR0GOZhe7SquI5LIRmYCFTo+3VBnItYeuin9i2xCIqWz886xIyllHN2BIPILbA1lCOsCsz1BRRGNqtLvmTeVRO8iujsHWBJicVgSI7/dgSJwcdOv2t4TIVtnN1hJkQnz+HZcJ2FYK/VWlo4UQYYoML52sBd1sSz/n8/8hrO2N4X9frHHNCrcxeoyChTKo2cm4rAxHylLbCZYvGt/KIW9x3AFkPBMr7tAc3yq98J0Crna8ukXc3F3uGb5QXLnBi//3zBDN6RCv7ByaFW5G0I+pglBegzeFBqKH8xwfy76B2e2VLFF8rz/r/wQzlumGFypsRhAoGxrkZyzjec/k+RNR0arf7TTX7ymC1cueTnItRDx89veW6WLlF53NpAGqC8GJSp4T2FGIIk01y29j6Ji7GOlQ8BUbyLWYjMfHf3khRzAfr6UC2QgVvKWQTKET4Y/b1nZCnwxeW8wC80GHtYGuarsU+KlsEw4242cjyIN1GobrWaA2GTOedQDEMWUA64McAw5fAvMEEao5DM7i57tMzJHeKfruyMuXYQkBca094vmATjJ/T+kIrWGIcmxCT/Fp2SW1hcxr6Ciwuog84LVfbVlUl2MAj3eC/xqL/5HP6Q3ObD0ld444GV+HSrQUqfIvEIn9gFmalW6TGugyhfROACCogoXbeIr1AyMUNDnl4EWlPl6u7SQvPX+itKyq4qhaK2J0W6f7ElLVQ5GbC2uwARuhXOi7mqEZ5FP0V675C5NPZOl2ZEd6BhmuyhGkmQEtEvw0DCKnbKM7bKMk90Y599DSnuEna4BNFBVjJ7k+BuNhXUKO+iNcDZT0pCQhOKRVLWsaqVff3BsuQ4zMEOVnccJwwAVipwSRyxZi8bF+Wyun6BVI8pz1CBvRMy+6ifmIq2awEL8NnV65hF2jyZDEVwsnrvCyT7MlM8l5C3MhqH/MgMcKqOsUz+P6Jv5sBi4WvojsaHzqxQ6miBHpHhGDpYH5K53LVs36henB/tOUTcg5ZnO4ZM67jjB7Oz7to+QnJsldp5Bdwvi1XD/4jeh/Llezu5/KwwytSHnZG1z6dZA7B8rKwnI+yN2Qnfi70h68jzGZ1xCOFPz9KMorNKP3XLw8x2g9H6lEBXdV95uc/TNw+WTJbvKRawns/DZhM1u/g13lU6JG19cht3dh/DlKRcJpj1AdOAxPiUubTSkhBmdwRj2BTTHrVlF3/9ladTP4s4f6Zj9TtQvR9CREVe7CboGflxDYC+Jww3PU50XLmxQjkuV5MkDAmBVcyFCFOcHhDRoxet4FX9ec0wjNeDpYtkI8B/qUS1Rp+is1jOxr4/ni|pabwMkF/SdYKdDlow4uKxaObrAP0urmv7N7fA9bedec="; + const accessTokenDecoded: DecodedAccessToken = { iss: "http://localhost", nbf: 1709324111, @@ -93,6 +106,7 @@ describe("TokenService", () => { keyGenerationService = mock(); encryptService = mock(); logService = mock(); + logoutCallback = jest.fn(); const supportsSecureStorage = false; // default to false; tests will override as needed tokenService = createTokenService(supportsSecureStorage); @@ -152,7 +166,7 @@ describe("TokenService", () => { expect(result).toEqual(true); }); - it("should return false if no access token exists in memory, disk, or secure storage", async () => { + it("returns false when no access token exists in memory, disk, or secure storage", async () => { // Act const result = await firstValueFrom(tokenService.hasAccessToken$(userIdFromAccessToken)); @@ -162,7 +176,7 @@ describe("TokenService", () => { }); describe("setAccessToken", () => { - it("should throw an error if the access token is null", async () => { + it("throws an error when the access token is null", async () => { // Act const result = tokenService.setAccessToken( null, @@ -173,7 +187,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Access token is required."); }); - it("should throw an error if an invalid token is passed in", async () => { + it("throws an error when an invalid token is passed in", async () => { // Act const result = tokenService.setAccessToken( "invalidToken", @@ -216,7 +230,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should set the access token in memory", async () => { + it("set the access token in memory", async () => { // Act await tokenService.setAccessToken( accessTokenJwt, @@ -246,6 +260,14 @@ describe("TokenService", () => { }); describe("Disk storage tests (secure storage supported on platform)", () => { + const accessTokenKey = new SymmetricCryptoKey( + new Uint8Array(64) as CsprngArray, + ) as AccessTokenKey; + + const accessTokenKeyB64 = { + keyB64: + "lI7lSoejJ1HsrTkRs2Ipm0x+YcZMKpgm7WQGCNjAWmFAyGOKossXwBJvvtbxcYDZ0G0XNY8Gp7DBXZV2tWAO5w==", + }; beforeEach(() => { const supportsSecureStorage = true; tokenService = createTokenService(supportsSecureStorage); @@ -259,7 +281,7 @@ describe("TokenService", () => { .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) .stateSubject.next([userIdFromAccessToken, accessTokenJwt]); - keyGenerationService.createKey.mockResolvedValue("accessTokenKey" as any); + keyGenerationService.createKey.mockResolvedValue(accessTokenKey); const mockEncryptedAccessToken = "encryptedAccessToken"; @@ -267,6 +289,11 @@ describe("TokenService", () => { encryptedString: mockEncryptedAccessToken, } as any); + // First call resolves to null to simulate no key in secure storage + // then resolves to the key to simulate the key being set in secure storage + // and retrieved successfully to ensure it was set. + secureStorageService.get.mockResolvedValueOnce(null).mockResolvedValue(accessTokenKeyB64); + // Act await tokenService.setAccessToken( accessTokenJwt, @@ -278,7 +305,7 @@ describe("TokenService", () => { // assert that the AccessTokenKey was set in secure storage expect(secureStorageService.save).toHaveBeenCalledWith( accessTokenKeySecureStorageKey, - "accessTokenKey", + accessTokenKey, secureStorageOptions, ); @@ -292,18 +319,85 @@ describe("TokenService", () => { singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY).nextMock, ).toHaveBeenCalledWith(null); }); + + it("should fallback to disk storage for the access token if the access token cannot be set in secure storage", async () => { + // This tests the scenario where the access token key silently fails to be set in secure storage + + // Arrange: + keyGenerationService.createKey.mockResolvedValue(accessTokenKey); + + // First call resolves to null to simulate no key in secure storage + // and then resolves to no key after it should have been set + secureStorageService.get.mockResolvedValueOnce(null).mockResolvedValue(null); + + // Act + await tokenService.setAccessToken( + accessTokenJwt, + diskVaultTimeoutAction, + diskVaultTimeout, + ); + // Assert + + // assert that we tried to store the AccessTokenKey in secure storage + expect(secureStorageService.save).toHaveBeenCalledWith( + accessTokenKeySecureStorageKey, + accessTokenKey, + secureStorageOptions, + ); + + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "SetAccessToken: storing encrypted access token in secure storage failed. Falling back to disk storage.", + new Error("New Access token key unable to be retrieved from secure storage."), + ); + + // assert that the access token was put on disk unencrypted + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(accessTokenJwt); + }); + + it("should fallback to disk storage for the access token if secure storage errors on trying to get an existing access token key", async () => { + // This tests the scenario for linux users who don't have secure storage configured. + + // Arrange: + keyGenerationService.createKey.mockResolvedValue(accessTokenKey); + + // Mock linux secure storage error + const secureStorageError = "Secure storage error"; + secureStorageService.get.mockRejectedValue(new Error(secureStorageError)); + + // Act + await tokenService.setAccessToken( + accessTokenJwt, + diskVaultTimeoutAction, + diskVaultTimeout, + ); + // Assert + + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "SetAccessToken: storing encrypted access token in secure storage failed. Falling back to disk storage.", + new Error(secureStorageError), + ); + + // assert that the access token was put on disk unencrypted + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(accessTokenJwt); + }); }); }); describe("getAccessToken", () => { - it("should return undefined if no user id is provided and there is no active user in global state", async () => { + it("returns null when no user id is provided and there is no active user in global state", async () => { // Act const result = await tokenService.getAccessToken(); // Assert - expect(result).toBeUndefined(); + expect(result).toBeNull(); }); - it("should return null if no access token is found in memory, disk, or secure storage", async () => { + it("returns null when no access token is found in memory, disk, or secure storage", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -317,11 +411,8 @@ describe("TokenService", () => { describe("Memory storage tests", () => { test.each([ - [ - "should get the access token from memory for the provided user id", - userIdFromAccessToken, - ], - ["should get the access token from memory with no user id provided", undefined], + ["gets the access token from memory when a user id is provided ", userIdFromAccessToken], + ["gets the access token from memory when no user id is provided", undefined], ])("%s", async (_, userId) => { // Arrange singleUserStateProvider @@ -350,11 +441,8 @@ describe("TokenService", () => { describe("Disk storage tests (secure storage not supported on platform)", () => { test.each([ - [ - "should get the access token from disk for the specified user id", - userIdFromAccessToken, - ], - ["should get the access token from disk with no user id specified", undefined], + ["gets the access token from disk when the user id is specified", userIdFromAccessToken], + ["gets the access token from disk when no user id is specified", undefined], ])("%s", async (_, userId) => { // Arrange singleUserStateProvider @@ -387,11 +475,11 @@ describe("TokenService", () => { test.each([ [ - "should get the encrypted access token from disk, decrypt it, and return it when user id is provided", + "gets the encrypted access token from disk, decrypts it, and returns it when a user id is provided", userIdFromAccessToken, ], [ - "should get the encrypted access token from disk, decrypt it, and return it when no user id is provided", + "gets the encrypted access token from disk, decrypts it, and returns it when no user id is provided", undefined, ], ])("%s", async (_, userId) => { @@ -423,11 +511,11 @@ describe("TokenService", () => { test.each([ [ - "should fallback and get the unencrypted access token from disk when there isn't an access token key in secure storage and a user id is provided", + "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and a user id is provided", userIdFromAccessToken, ], [ - "should fallback and get the unencrypted access token from disk when there isn't an access token key in secure storage and no user id is provided", + "falls back and gets the unencrypted access token from disk when there isn't an access token key in secure storage and no user id is provided", undefined, ], ])("%s", async (_, userId) => { @@ -455,11 +543,80 @@ describe("TokenService", () => { // Assert expect(result).toEqual(accessTokenJwt); }); + + it("logs the error and logs the user out when the access token key cannot be retrieved from secure storage if the access token is encrypted", async () => { + // This tests the intermittent windows 10/11 scenario in which the access token key was stored successfully in secure storage and the + // access token was encrypted with it and stored on disk successfully. However, on retrieval the access token key isn't able to + // retrieved for whatever reason. + + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, encryptedAccessToken]); + + // No access token key set + + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "Access token key not found to decrypt encrypted access token. Logging user out.", + ); + + // assert that we logged the user out + expect(logoutCallback).toHaveBeenCalledWith( + "accessTokenUnableToBeDecrypted", + userIdFromAccessToken, + ); + }); + + it("logs the error and logs the user out when secure storage errors on trying to get an access token key", async () => { + // This tests the linux scenario where users might not have secure storage support configured. + + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, encryptedAccessToken]); + + // Mock linux secure storage error + const secureStorageError = "Secure storage error"; + secureStorageService.get.mockRejectedValue(new Error(secureStorageError)); + + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "Access token key retrieval failed. Unable to decrypt encrypted access token. Logging user out.", + new Error(secureStorageError), + ); + + // assert that we logged the user out + expect(logoutCallback).toHaveBeenCalledWith( + "accessTokenUnableToBeDecrypted", + userIdFromAccessToken, + ); + }); }); }); describe("clearAccessToken", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = tokenService.clearAccessToken(); @@ -475,11 +632,11 @@ describe("TokenService", () => { test.each([ [ - "should clear the access token from all storage locations for the provided user id", + "clears the access token from all storage locations when a user id is provided", userIdFromAccessToken, ], [ - "should clear the access token from all storage locations for the global active user", + "clears the access token from all storage locations when there is a global active user", undefined, ], ])("%s", async (_, userId) => { @@ -519,7 +676,7 @@ describe("TokenService", () => { }); describe("decodeAccessToken", () => { - it("should throw an error if no access token provided or retrieved from state", async () => { + it("throws an error when no access token is provided or retrievable from state", async () => { // Access tokenService.getAccessToken = jest.fn().mockResolvedValue(null); @@ -530,7 +687,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Access token not found."); }); - it("should decode the access token", async () => { + it("decodes the access token when a valid one is stored", async () => { // Arrange tokenService.getAccessToken = jest.fn().mockResolvedValue(accessTokenJwt); @@ -544,7 +701,7 @@ describe("TokenService", () => { describe("Data methods", () => { describe("getTokenExpirationDate", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -555,7 +712,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should return null if the decoded access token is null", async () => { + it("returns null when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -566,7 +723,7 @@ describe("TokenService", () => { expect(result).toBeNull(); }); - it("should return null if the decoded access token does not have an expiration date", async () => { + it("returns null when the decoded access token does not have an expiration date", async () => { // Arrange const accessTokenDecodedWithoutExp = { ...accessTokenDecoded }; delete accessTokenDecodedWithoutExp.exp; @@ -581,7 +738,7 @@ describe("TokenService", () => { expect(result).toBeNull(); }); - it("should return null if the decoded access token has an non numeric expiration date", async () => { + it("returns null when the decoded access token has a non numeric expiration date", async () => { // Arrange const accessTokenDecodedWithNonNumericExp = { ...accessTokenDecoded, exp: "non-numeric" }; tokenService.decodeAccessToken = jest @@ -595,7 +752,7 @@ describe("TokenService", () => { expect(result).toBeNull(); }); - it("should return the expiration date of the access token", async () => { + it("returns the expiration date of the access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -608,7 +765,7 @@ describe("TokenService", () => { }); describe("tokenSecondsRemaining", () => { - it("should return 0 if the tokenExpirationDate is null", async () => { + it("returns 0 when the tokenExpirationDate is null", async () => { // Arrange tokenService.getTokenExpirationDate = jest.fn().mockResolvedValue(null); @@ -619,7 +776,7 @@ describe("TokenService", () => { expect(result).toEqual(0); }); - it("should return the number of seconds remaining until the token expires", async () => { + it("returns the number of seconds remaining until the token expires", async () => { // Arrange // Lock the time to ensure a consistent test environment // otherwise we have flaky issues with set system time date and the Date.now() call. @@ -644,7 +801,7 @@ describe("TokenService", () => { jest.useRealTimers(); }); - it("should return the number of seconds remaining until the token expires, considering an offset", async () => { + it("returns the number of seconds remaining until the token expires when given an offset", async () => { // Arrange // Lock the time to ensure a consistent test environment // otherwise we have flaky issues with set system time date and the Date.now() call. @@ -672,7 +829,7 @@ describe("TokenService", () => { }); describe("tokenNeedsRefresh", () => { - it("should return true if token is within the default refresh threshold (5 min)", async () => { + it("returns true when the token is within the default refresh threshold (5 min)", async () => { // Arrange const tokenSecondsRemaining = 60; tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining); @@ -684,7 +841,7 @@ describe("TokenService", () => { expect(result).toEqual(true); }); - it("should return false if token is outside the default refresh threshold (5 min)", async () => { + it("returns false when the token is outside the default refresh threshold (5 min)", async () => { // Arrange const tokenSecondsRemaining = 600; tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining); @@ -696,7 +853,7 @@ describe("TokenService", () => { expect(result).toEqual(false); }); - it("should return true if token is within the specified refresh threshold", async () => { + it("returns true when the token is within the specified refresh threshold", async () => { // Arrange const tokenSecondsRemaining = 60; tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining); @@ -708,7 +865,7 @@ describe("TokenService", () => { expect(result).toEqual(true); }); - it("should return false if token is outside the specified refresh threshold", async () => { + it("returns false when the token is outside the specified refresh threshold", async () => { // Arrange const tokenSecondsRemaining = 600; tokenService.tokenSecondsRemaining = jest.fn().mockResolvedValue(tokenSecondsRemaining); @@ -722,7 +879,7 @@ describe("TokenService", () => { }); describe("getUserId", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -733,7 +890,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should throw an error if the decoded access token is null", async () => { + it("throws an error when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -744,7 +901,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No user id found"); }); - it("should throw an error if the decoded access token has a non-string user id", async () => { + it("throws an error when the decoded access token has a non-string user id", async () => { // Arrange const accessTokenDecodedWithNonStringSub = { ...accessTokenDecoded, sub: 123 }; tokenService.decodeAccessToken = jest @@ -758,7 +915,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No user id found"); }); - it("should return the user id from the decoded access token", async () => { + it("returns the user id from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -771,7 +928,7 @@ describe("TokenService", () => { }); describe("getUserIdFromAccessToken", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -782,7 +939,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should throw an error if the decoded access token is null", async () => { + it("throws an error when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -793,7 +950,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No user id found"); }); - it("should throw an error if the decoded access token has a non-string user id", async () => { + it("throws an error when the decoded access token has a non-string user id", async () => { // Arrange const accessTokenDecodedWithNonStringSub = { ...accessTokenDecoded, sub: 123 }; tokenService.decodeAccessToken = jest @@ -807,7 +964,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No user id found"); }); - it("should return the user id from the decoded access token", async () => { + it("returns the user id from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -820,7 +977,7 @@ describe("TokenService", () => { }); describe("getEmail", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -831,7 +988,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should throw an error if the decoded access token is null", async () => { + it("throws an error when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -842,7 +999,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No email found"); }); - it("should throw an error if the decoded access token has a non-string email", async () => { + it("throws an error when the decoded access token has a non-string email", async () => { // Arrange const accessTokenDecodedWithNonStringEmail = { ...accessTokenDecoded, email: 123 }; tokenService.decodeAccessToken = jest @@ -856,7 +1013,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No email found"); }); - it("should return the email from the decoded access token", async () => { + it("returns the email from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -869,7 +1026,7 @@ describe("TokenService", () => { }); describe("getEmailVerified", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -880,7 +1037,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should throw an error if the decoded access token is null", async () => { + it("throws an error when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -891,7 +1048,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No email verification found"); }); - it("should throw an error if the decoded access token has a non-boolean email_verified", async () => { + it("throws an error when the decoded access token has a non-boolean email_verified", async () => { // Arrange const accessTokenDecodedWithNonBooleanEmailVerified = { ...accessTokenDecoded, @@ -908,7 +1065,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No email verification found"); }); - it("should return the email_verified from the decoded access token", async () => { + it("returns the email_verified from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -921,7 +1078,7 @@ describe("TokenService", () => { }); describe("getName", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -932,7 +1089,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should return null if the decoded access token is null", async () => { + it("returns null when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -943,7 +1100,7 @@ describe("TokenService", () => { expect(result).toBeNull(); }); - it("should return null if the decoded access token has a non-string name", async () => { + it("returns null when the decoded access token has a non-string name", async () => { // Arrange const accessTokenDecodedWithNonStringName = { ...accessTokenDecoded, name: 123 }; tokenService.decodeAccessToken = jest @@ -957,7 +1114,7 @@ describe("TokenService", () => { expect(result).toBeNull(); }); - it("should return the name from the decoded access token", async () => { + it("returns the name from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -970,7 +1127,7 @@ describe("TokenService", () => { }); describe("getIssuer", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -981,7 +1138,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should throw an error if the decoded access token is null", async () => { + it("throws an error when the decoded access token is null", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(null); @@ -992,7 +1149,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No issuer found"); }); - it("should throw an error if the decoded access token has a non-string iss", async () => { + it("throws an error when the decoded access token has a non-string iss", async () => { // Arrange const accessTokenDecodedWithNonStringIss = { ...accessTokenDecoded, iss: 123 }; tokenService.decodeAccessToken = jest @@ -1006,7 +1163,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("No issuer found"); }); - it("should return the issuer from the decoded access token", async () => { + it("returns the issuer from the decoded access token when a valid access token is stored", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockResolvedValue(accessTokenDecoded); @@ -1019,7 +1176,7 @@ describe("TokenService", () => { }); describe("getIsExternal", () => { - it("should throw an error if the access token cannot be decoded", async () => { + it("throws an error when the access token cannot be decoded", async () => { // Arrange tokenService.decodeAccessToken = jest.fn().mockRejectedValue(new Error("Mock error")); @@ -1030,7 +1187,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Failed to decode access token: Mock error"); }); - it("should return false if the amr (Authentication Method Reference) claim does not contain 'external'", async () => { + it("returns false when the amr (Authentication Method Reference) claim does not contain 'external'", async () => { // Arrange const accessTokenDecodedWithoutExternalAmr = { ...accessTokenDecoded, @@ -1047,7 +1204,7 @@ describe("TokenService", () => { expect(result).toEqual(false); }); - it("should return true if the amr (Authentication Method Reference) claim contains 'external'", async () => { + it("returns true when the amr (Authentication Method Reference) claim contains 'external'", async () => { // Arrange const accessTokenDecodedWithExternalAmr = { ...accessTokenDecoded, @@ -1073,7 +1230,7 @@ describe("TokenService", () => { const refreshTokenSecureStorageKey = `${userIdFromAccessToken}${refreshTokenPartialSecureStorageKey}`; describe("setRefreshToken", () => { - it("should throw an error if no user id is provided", async () => { + it("throws an error when no user id is provided", async () => { // Act // note: don't await here because we want to test the error const result = (tokenService as any).setRefreshToken( @@ -1113,7 +1270,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should set the refresh token in memory for the specified user id", async () => { + it("sets the refresh token in memory when given a user id", async () => { // Act await (tokenService as any).setRefreshToken( refreshToken, @@ -1130,7 +1287,7 @@ describe("TokenService", () => { }); describe("Disk storage tests (secure storage not supported on platform)", () => { - it("should set the refresh token in disk for the specified user id", async () => { + it("sets the refresh token in disk when given a user id", async () => { // Act await (tokenService as any).setRefreshToken( refreshToken, @@ -1152,7 +1309,7 @@ describe("TokenService", () => { tokenService = createTokenService(supportsSecureStorage); }); - it("should set the refresh token in secure storage, null out data on disk or in memory, and set a flag to indicate the token has been migrated for the specified user id", async () => { + it("sets the refresh token in secure storage, removes data on disk or in memory, and sets a flag to indicate the token has been migrated when given a user id", async () => { // Arrange: // For testing purposes, let's assume that the token is already in disk and memory singleUserStateProvider @@ -1163,6 +1320,9 @@ describe("TokenService", () => { .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) .stateSubject.next([userIdFromAccessToken, refreshToken]); + // We immediately call to get the refresh token from secure storage after setting it to ensure it was set. + secureStorageService.get.mockResolvedValue(refreshToken); + // Act await (tokenService as any).setRefreshToken( refreshToken, @@ -1187,18 +1347,166 @@ describe("TokenService", () => { singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY).nextMock, ).toHaveBeenCalledWith(null); }); + + it("tries to set the refresh token in secure storage then falls back to disk storage when the refresh token cannot be read back out of secure storage", async () => { + // Arrange: + // We immediately call to get the refresh token from secure storage after setting it to ensure it was set. + // So, set it to return null to mock a failure to set the refresh token in secure storage. + // This mocks the windows 10/11 intermittent issue where the token is not set in secure storage successfully. + secureStorageService.get.mockResolvedValue(null); + + // Act + await (tokenService as any).setRefreshToken( + refreshToken, + diskVaultTimeoutAction, + diskVaultTimeout, + userIdFromAccessToken, + ); + // Assert + + // assert that the refresh token was set in secure storage + expect(secureStorageService.save).toHaveBeenCalledWith( + refreshTokenSecureStorageKey, + refreshToken, + secureStorageOptions, + ); + + // assert that we tried to set the refresh token in secure storage, but it failed, so we reverted to disk storage + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(refreshToken); + }); + + it("tries to set the refresh token in secure storage, throws an error, then falls back to disk storage when secure storage isn't supported", async () => { + // Arrange: + // Mock the secure storage service to throw an error when trying to save the refresh token + // to simulate linux scenarios where a secure storage provider isn't configured. + secureStorageService.save.mockRejectedValue(new Error("Secure storage not supported")); + + // Act + await (tokenService as any).setRefreshToken( + refreshToken, + diskVaultTimeoutAction, + diskVaultTimeout, + userIdFromAccessToken, + ); + // Assert + + // assert that the refresh token was set in secure storage + expect(secureStorageService.save).toHaveBeenCalledWith( + refreshTokenSecureStorageKey, + refreshToken, + secureStorageOptions, + ); + + // assert that we tried to set the refresh token in secure storage, but it failed, so we reverted to disk storage + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(refreshToken); + }); + + it("returns the unencrypted access token when secure storage retrieval fails but the access token is still pre-migration", async () => { + // This tests the linux scenario where users might not have secure storage support configured. + + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, accessTokenJwt]); + + // Mock linux secure storage error + const secureStorageError = "Secure storage error"; + secureStorageService.get.mockRejectedValue(new Error(secureStorageError)); + + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); + + // Assert + // assert that we returned the unencrypted, pre-migration access token + expect(result).toBe(accessTokenJwt); + + // assert that we did not log an error or log the user out + expect(logService.error).not.toHaveBeenCalled(); + + expect(logoutCallback).not.toHaveBeenCalled(); + }); + + it("does not error and fallback to disk storage when passed a null value for the refresh token", async () => { + // Arrange + secureStorageService.get.mockResolvedValue(null); + + // Act + await (tokenService as any).setRefreshToken( + null, + diskVaultTimeoutAction, + diskVaultTimeout, + userIdFromAccessToken, + ); + + // Assert + expect(secureStorageService.save).toHaveBeenCalledWith( + refreshTokenSecureStorageKey, + null, + secureStorageOptions, + ); + + expect(logService.error).not.toHaveBeenCalled(); + + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK).nextMock, + ).toHaveBeenCalledWith(null); + + expect( + singleUserStateProvider.getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY).nextMock, + ).toHaveBeenCalledWith(null); + }); + + it("logs the error and logs the user out when the access token cannot be decrypted", async () => { + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, ACCESS_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, encryptedAccessToken]); + + secureStorageService.get.mockResolvedValue(accessTokenKeyB64); + encryptService.decryptToUtf8.mockRejectedValue(new Error("Decryption error")); + + // Act + const result = await tokenService.getAccessToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + + // assert that we logged the error + expect(logService.error).toHaveBeenCalledWith( + "Failed to decrypt access token", + new Error("Decryption error"), + ); + + // assert that we logged the user out + expect(logoutCallback).toHaveBeenCalledWith( + "accessTokenUnableToBeDecrypted", + userIdFromAccessToken, + ); + }); }); }); describe("getRefreshToken", () => { - it("should return undefined if no user id is provided and there is no active user in global state", async () => { + it("returns null when no user id is provided and there is no active user in global state", async () => { // Act const result = await (tokenService as any).getRefreshToken(); // Assert - expect(result).toBeUndefined(); + expect(result).toBeNull(); }); - it("should return null if no refresh token is found in memory, disk, or secure storage", async () => { + it("returns null when no refresh token is found in memory, disk, or secure storage", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1211,7 +1519,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should get the refresh token from memory with no user id specified (uses global active user)", async () => { + it("gets the refresh token from memory when no user id is specified (uses global active user)", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1233,7 +1541,7 @@ describe("TokenService", () => { expect(result).toEqual(refreshToken); }); - it("should get the refresh token from memory for the specified user id", async () => { + it("gets the refresh token from memory when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1251,7 +1559,7 @@ describe("TokenService", () => { }); describe("Disk storage tests (secure storage not supported on platform)", () => { - it("should get the refresh token from disk with no user id specified", async () => { + it("gets the refresh token from disk when no user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1272,7 +1580,7 @@ describe("TokenService", () => { expect(result).toEqual(refreshToken); }); - it("should get the refresh token from disk for the specified user id", async () => { + it("gets the refresh token from disk when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1295,7 +1603,7 @@ describe("TokenService", () => { tokenService = createTokenService(supportsSecureStorage); }); - it("should get the refresh token from secure storage when no user id is specified and the migration flag is set to true", async () => { + it("gets the refresh token from secure storage when no user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1318,7 +1626,7 @@ describe("TokenService", () => { expect(result).toEqual(refreshToken); }); - it("should get the refresh token from secure storage when user id is specified and the migration flag set to true", async () => { + it("gets the refresh token from secure storage when a user id is specified", async () => { // Arrange singleUserStateProvider @@ -1337,7 +1645,7 @@ describe("TokenService", () => { expect(result).toEqual(refreshToken); }); - it("should fallback and get the refresh token from disk when user id is specified and the migration flag is set to false even if the platform supports secure storage", async () => { + it("falls back and gets the refresh token from disk when a user id is specified even if the platform supports secure storage", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1357,7 +1665,7 @@ describe("TokenService", () => { expect(secureStorageService.get).not.toHaveBeenCalled(); }); - it("should fallback and get the refresh token from disk when no user id is specified and the migration flag is set to false even if the platform supports secure storage", async () => { + it("falls back and gets the refresh token from disk when no user id is specified even if the platform supports secure storage", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1381,11 +1689,80 @@ describe("TokenService", () => { // assert that secure storage was not called expect(secureStorageService.get).not.toHaveBeenCalled(); }); + + it("returns null when the refresh token is not found in memory, on disk, or in secure storage", async () => { + // Arrange + secureStorageService.get.mockResolvedValue(null); + + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + }); + + it("returns null and logs when the refresh token is not found in secure storage when it should be", async () => { + // This scenario mocks the case where we have intermittent windows 10/11 issues w/ secure storage not + // returning the refresh token when it should be there. + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, undefined]); + + secureStorageService.get.mockResolvedValue(null); + + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + + expect(logService.error).toHaveBeenCalledWith( + "Refresh token not found in secure storage. Access token will fail to refresh upon expiration or manual refresh.", + ); + }); + + it("logs out when retrieving the refresh token out of secure storage errors", async () => { + // This scenario mocks the case where linux users don't have secure storage configured. + // Arrange + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) + .stateSubject.next([userIdFromAccessToken, undefined]); + + singleUserStateProvider + .getFake(userIdFromAccessToken, REFRESH_TOKEN_DISK) + .stateSubject.next([userIdFromAccessToken, undefined]); + + const secureStorageSvcMockErrorMsg = "Secure storage retrieval error"; + + secureStorageService.get.mockRejectedValue(new Error(secureStorageSvcMockErrorMsg)); + + // Act + const result = await tokenService.getRefreshToken(userIdFromAccessToken); + + // Assert + expect(result).toBeNull(); + + // expect that we logged an error and logged the user out + expect(logService.error).toHaveBeenCalledWith( + `Failed to retrieve refresh token from secure storage`, + new Error(secureStorageSvcMockErrorMsg), + ); + + expect(logoutCallback).toHaveBeenCalledWith( + "refreshTokenSecureStorageRetrievalFailure", + userIdFromAccessToken, + ); + }); }); }); describe("clearRefreshToken", () => { - it("should throw an error if no user id is provided", async () => { + it("throws an error when no user id is provided", async () => { // Act // note: don't await here because we want to test the error const result = (tokenService as any).clearRefreshToken(); @@ -1399,7 +1776,7 @@ describe("TokenService", () => { tokenService = createTokenService(supportsSecureStorage); }); - it("should clear the refresh token from all storage locations for the specified user id", async () => { + it("clears the refresh token from all storage locations when given a user id", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, REFRESH_TOKEN_MEMORY) @@ -1433,7 +1810,7 @@ describe("TokenService", () => { const clientId = "clientId"; describe("setClientId", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = tokenService.setClientId(clientId, VaultTimeoutAction.Lock, null); @@ -1470,7 +1847,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should set the client id in memory when there is an active user in global state", async () => { + it("sets the client id in memory when there is an active user in global state", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1486,7 +1863,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(clientId); }); - it("should set the client id in memory for the specified user id", async () => { + it("sets the client id in memory when given a user id", async () => { // Act await tokenService.setClientId( clientId, @@ -1504,7 +1881,7 @@ describe("TokenService", () => { }); describe("Disk storage tests", () => { - it("should set the client id in disk when there is an active user in global state", async () => { + it("sets the client id in disk when there is an active user in global state", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1519,7 +1896,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(clientId); }); - it("should set the client id in disk for the specified user id", async () => { + it("sets the client id on disk when given a user id", async () => { // Act await tokenService.setClientId( clientId, @@ -1537,14 +1914,14 @@ describe("TokenService", () => { }); describe("getClientId", () => { - it("should return undefined if no user id is provided and there is no active user in global state", async () => { + it("returns undefined when no user id is provided and there is no active user in global state", async () => { // Act const result = await tokenService.getClientId(); // Assert expect(result).toBeUndefined(); }); - it("should return null if no client id is found in memory or disk", async () => { + it("returns null when no client id is found in memory or disk", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1557,7 +1934,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should get the client id from memory with no user id specified (uses global active user)", async () => { + it("gets the client id from memory when no user id is specified (uses global active user)", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1580,7 +1957,7 @@ describe("TokenService", () => { expect(result).toEqual(clientId); }); - it("should get the client id from memory for the specified user id", async () => { + it("gets the client id from memory when given a user id", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1599,7 +1976,7 @@ describe("TokenService", () => { }); describe("Disk storage tests", () => { - it("should get the client id from disk with no user id specified", async () => { + it("gets the client id from disk when no user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1620,7 +1997,7 @@ describe("TokenService", () => { expect(result).toEqual(clientId); }); - it("should get the client id from disk for the specified user id", async () => { + it("gets the client id from disk when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1639,7 +2016,7 @@ describe("TokenService", () => { }); describe("clearClientId", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = (tokenService as any).clearClientId(); @@ -1647,7 +2024,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot clear client id."); }); - it("should clear the client id from memory and disk for the specified user id", async () => { + it("clears the client id from memory and disk when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1669,7 +2046,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(null); }); - it("should clear the client id from memory and disk for the global active user", async () => { + it("clears the client id from memory and disk when there is a global active user", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_ID_MEMORY) @@ -1702,7 +2079,7 @@ describe("TokenService", () => { const clientSecret = "clientSecret"; describe("setClientSecret", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = tokenService.setClientSecret( @@ -1747,7 +2124,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should set the client secret in memory when there is an active user in global state", async () => { + it("sets the client secret in memory when there is an active user in global state", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1767,7 +2144,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(clientSecret); }); - it("should set the client secret in memory for the specified user id", async () => { + it("sets the client secret in memory when a user id is specified", async () => { // Act await tokenService.setClientSecret( clientSecret, @@ -1785,7 +2162,7 @@ describe("TokenService", () => { }); describe("Disk storage tests", () => { - it("should set the client secret in disk when there is an active user in global state", async () => { + it("sets the client secret on disk when there is an active user in global state", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1805,7 +2182,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(clientSecret); }); - it("should set the client secret in disk for the specified user id", async () => { + it("sets the client secret on disk when a user id is specified", async () => { // Act await tokenService.setClientSecret( clientSecret, @@ -1824,14 +2201,14 @@ describe("TokenService", () => { }); describe("getClientSecret", () => { - it("should return undefined if no user id is provided and there is no active user in global state", async () => { + it("returns undefined when no user id is provided and there is no active user in global state", async () => { // Act const result = await tokenService.getClientSecret(); // Assert expect(result).toBeUndefined(); }); - it("should return null if no client secret is found in memory or disk", async () => { + it("returns null when no client secret is found in memory or disk", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -1844,7 +2221,7 @@ describe("TokenService", () => { }); describe("Memory storage tests", () => { - it("should get the client secret from memory with no user id specified (uses global active user)", async () => { + it("gets the client secret from memory when no user id is specified (uses global active user)", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1867,7 +2244,7 @@ describe("TokenService", () => { expect(result).toEqual(clientSecret); }); - it("should get the client secret from memory for the specified user id", async () => { + it("gets the client secret from memory when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1886,7 +2263,7 @@ describe("TokenService", () => { }); describe("Disk storage tests", () => { - it("should get the client secret from disk with no user id specified", async () => { + it("gets the client secret from disk when no user id specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1907,7 +2284,7 @@ describe("TokenService", () => { expect(result).toEqual(clientSecret); }); - it("should get the client secret from disk for the specified user id", async () => { + it("gets the client secret from disk when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1926,7 +2303,7 @@ describe("TokenService", () => { }); describe("clearClientSecret", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = (tokenService as any).clearClientSecret(); @@ -1934,7 +2311,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot clear client secret."); }); - it("should clear the client secret from memory and disk for the specified user id", async () => { + it("clears the client secret from memory and disk when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1958,7 +2335,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(null); }); - it("should clear the client secret from memory and disk for the global active user", async () => { + it("clears the client secret from memory and disk when there is a global active user", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, API_KEY_CLIENT_SECRET_MEMORY) @@ -1990,7 +2367,7 @@ describe("TokenService", () => { }); describe("setTokens", () => { - it("should call to set all passed in tokens after deriving user id from the access token", async () => { + it("calls to set all tokens after deriving user id from the access token when called with valid params", async () => { // Arrange const refreshToken = "refreshToken"; // specific vault timeout actions and vault timeouts don't change this test so values don't matter. @@ -2042,7 +2419,7 @@ describe("TokenService", () => { ); }); - it("should not try to set client id and client secret if they are not passed in", async () => { + it("does not try to set client id and client secret when they are not passed in", async () => { // Arrange const refreshToken = "refreshToken"; const vaultTimeoutAction = VaultTimeoutAction.Lock; @@ -2076,7 +2453,7 @@ describe("TokenService", () => { expect(tokenService.setClientSecret).not.toHaveBeenCalled(); }); - it("should throw an error if the access token is invalid", async () => { + it("throws an error when the access token is invalid", async () => { // Arrange const accessToken = "invalidToken"; const refreshToken = "refreshToken"; @@ -2095,7 +2472,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("JWT must have 3 parts"); }); - it("should throw an error if the access token is missing", async () => { + it("throws an error when the access token is missing", async () => { // Arrange const accessToken: string = null; const refreshToken = "refreshToken"; @@ -2150,7 +2527,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("Vault Timeout Action is required."); }); - it("should not throw an error if the refresh token is missing and it should just not set it", async () => { + it("does not throw an error or set the refresh token when the refresh token is missing", async () => { // Arrange const refreshToken: string = null; const vaultTimeoutAction = VaultTimeoutAction.Lock; @@ -2166,7 +2543,7 @@ describe("TokenService", () => { }); describe("clearTokens", () => { - it("should call to clear all tokens for the specified user id", async () => { + it("calls to clear all tokens when given a specified user id", async () => { // Arrange const userId = "userId" as UserId; @@ -2187,7 +2564,7 @@ describe("TokenService", () => { expect((tokenService as any).clearClientSecret).toHaveBeenCalledWith(userId); }); - it("should call to clear all tokens for the active user id", async () => { + it("calls to clear all tokens when there is an active user", async () => { // Arrange const userId = "userId" as UserId; @@ -2210,7 +2587,7 @@ describe("TokenService", () => { expect((tokenService as any).clearClientSecret).toHaveBeenCalledWith(userId); }); - it("should not call to clear all tokens if no user id is provided and there is no active user in global state", async () => { + it("does not call to clear all tokens when no user id is provided and there is no active user in global state", async () => { // Arrange tokenService.clearAccessToken = jest.fn(); (tokenService as any).clearRefreshToken = jest.fn(); @@ -2228,7 +2605,7 @@ describe("TokenService", () => { describe("Two Factor Token methods", () => { describe("setTwoFactorToken", () => { - it("should set the email and two factor token when there hasn't been a previous record (initializing the record)", async () => { + it("sets the email and two factor token when there hasn't been a previous record (initializing the record)", async () => { // Arrange const email = "testUser@email.com"; const twoFactorToken = "twoFactorTokenForTestUser"; @@ -2240,7 +2617,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith({ [email]: twoFactorToken }); }); - it("should set the email and two factor token when there is an initialized value already (updating the existing record)", async () => { + it("sets the email and two factor token when there is an initialized value already (updating the existing record)", async () => { // Arrange const email = "testUser@email.com"; const twoFactorToken = "twoFactorTokenForTestUser"; @@ -2263,7 +2640,7 @@ describe("TokenService", () => { }); describe("getTwoFactorToken", () => { - it("should return the two factor token for the given email", async () => { + it("returns the two factor token when given an email", async () => { // Arrange const email = "testUser"; const twoFactorToken = "twoFactorTokenForTestUser"; @@ -2282,7 +2659,7 @@ describe("TokenService", () => { expect(result).toEqual(twoFactorToken); }); - it("should not return the two factor token for an email that doesn't exist", async () => { + it("does not return the two factor token when given an email that doesn't exist", async () => { // Arrange const email = "testUser"; const initialTwoFactorTokenRecord: Record = { @@ -2300,7 +2677,7 @@ describe("TokenService", () => { expect(result).toEqual(undefined); }); - it("should return null if there is no two factor token record", async () => { + it("returns null when there is no two factor token record", async () => { // Arrange globalStateProvider .getFake(EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL) @@ -2315,7 +2692,7 @@ describe("TokenService", () => { }); describe("clearTwoFactorToken", () => { - it("should clear the two factor token for the given email when a record exists", async () => { + it("clears the two factor token for the given email when a record exists", async () => { // Arrange const email = "testUser"; const twoFactorToken = "twoFactorTokenForTestUser"; @@ -2336,7 +2713,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith({}); }); - it("should initialize the record if it doesn't exist and delete the value", async () => { + it("initializes the record and deletes the value when the record doesn't exist", async () => { // Arrange const email = "testUser"; @@ -2355,7 +2732,7 @@ describe("TokenService", () => { const mockSecurityStamp = "securityStamp"; describe("setSecurityStamp", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error deletes the value no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = tokenService.setSecurityStamp(mockSecurityStamp); @@ -2363,7 +2740,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot set security stamp."); }); - it("should set the security stamp in memory when there is an active user in global state", async () => { + it("sets the security stamp in memory when there is an active user in global state", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -2378,7 +2755,7 @@ describe("TokenService", () => { ).toHaveBeenCalledWith(mockSecurityStamp); }); - it("should set the security stamp in memory for the specified user id", async () => { + it("sets the security stamp in memory when a user id is specified", async () => { // Act await tokenService.setSecurityStamp(mockSecurityStamp, userIdFromAccessToken); @@ -2390,7 +2767,7 @@ describe("TokenService", () => { }); describe("getSecurityStamp", () => { - it("should throw an error if no user id is provided and there is no active user in global state", async () => { + it("throws an error when no user id is provided and there is no active user in global state", async () => { // Act // note: don't await here because we want to test the error const result = tokenService.getSecurityStamp(); @@ -2398,7 +2775,7 @@ describe("TokenService", () => { await expect(result).rejects.toThrow("User id not found. Cannot get security stamp."); }); - it("should return the security stamp from memory with no user id specified (uses global active user)", async () => { + it("returns the security stamp from memory when no user id is specified (uses global active user)", async () => { // Arrange globalStateProvider .getFake(ACCOUNT_ACTIVE_ACCOUNT_ID) @@ -2415,7 +2792,7 @@ describe("TokenService", () => { expect(result).toEqual(mockSecurityStamp); }); - it("should return the security stamp from memory for the specified user id", async () => { + it("returns the security stamp from memory when a user id is specified", async () => { // Arrange singleUserStateProvider .getFake(userIdFromAccessToken, SECURITY_STAMP_MEMORY) @@ -2601,6 +2978,7 @@ describe("TokenService", () => { keyGenerationService, encryptService, logService, + logoutCallback, ); } }); diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index 203d95429ee..38d0a77b52f 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -1,7 +1,7 @@ import { Observable, combineLatest, firstValueFrom, map } from "rxjs"; import { Opaque } from "type-fest"; -import { decodeJwtTokenToJson } from "@bitwarden/auth/common"; +import { LogoutReason, decodeJwtTokenToJson } from "@bitwarden/auth/common"; import { VaultTimeoutAction } from "../../enums/vault-timeout-action.enum"; import { EncryptService } from "../../platform/abstractions/encrypt.service"; @@ -111,7 +111,7 @@ export type DecodedAccessToken = { * A symmetric key for encrypting the access token before the token is stored on disk. * This key should be stored in secure storage. * */ -type AccessTokenKey = Opaque; +export type AccessTokenKey = Opaque; export class TokenService implements TokenServiceAbstraction { private readonly accessTokenKeySecureStorageKey: string = "_accessTokenKey"; @@ -132,6 +132,7 @@ export class TokenService implements TokenServiceAbstraction { private keyGenerationService: KeyGenerationService, private encryptService: EncryptService, private logService: LogService, + private logoutCallback: (logoutReason: LogoutReason, userId?: string) => Promise, ) { this.initializeState(); } @@ -145,10 +146,6 @@ export class TokenService implements TokenServiceAbstraction { ]).pipe(map(([disk, memory]) => Boolean(disk || memory))); } - // pivoting to an approach where we create a symmetric key we store in secure storage - // which is used to protect the data before persisting to disk. - // We will also use the same symmetric key to decrypt the data when reading from disk. - private initializeState(): void { this.emailTwoFactorTokenRecordGlobalState = this.globalStateProvider.get( EMAIL_TWO_FACTOR_TOKEN_RECORD_DISK_LOCAL, @@ -218,6 +215,14 @@ export class TokenService implements TokenServiceAbstraction { this.getSecureStorageOptions(userId), ); + // We are having intermittent issues with access token keys not saving into secure storage on windows 10/11. + // So, let's add a check to ensure we can read the value after writing it. + const accessTokenKey = await this.getAccessTokenKey(userId); + + if (!accessTokenKey) { + throw new Error("New Access token key unable to be retrieved from secure storage."); + } + return newAccessTokenKey; } @@ -238,6 +243,8 @@ export class TokenService implements TokenServiceAbstraction { } // First see if we have an accessTokenKey in secure storage and return it if we do + // Note: retrieving/saving data from/to secure storage on linux will throw if the + // distro doesn't have a secure storage provider let accessTokenKey: AccessTokenKey = await this.getAccessTokenKey(userId); if (!accessTokenKey) { @@ -255,15 +262,13 @@ export class TokenService implements TokenServiceAbstraction { } private async decryptAccessToken( + accessTokenKey: AccessTokenKey, encryptedAccessToken: EncString, - userId: UserId, ): Promise { - const accessTokenKey = await this.getAccessTokenKey(userId); - if (!accessTokenKey) { - // If we don't have an accessTokenKey, then that means we don't have an access token as it hasn't been set yet - // and we have to return null here to properly indicate the user isn't logged in. - return null; + throw new Error( + "decryptAccessToken: Access token key required. Cannot decrypt access token.", + ); } const decryptedAccessToken = await this.encryptService.decryptToUtf8( @@ -297,17 +302,32 @@ export class TokenService implements TokenServiceAbstraction { // store the access token directly. Instead, we encrypt with accessTokenKey and store that // in secure storage. - const encryptedAccessToken: EncString = await this.encryptAccessToken(accessToken, userId); + try { + const encryptedAccessToken: EncString = await this.encryptAccessToken( + accessToken, + userId, + ); - // Save the encrypted access token to disk - await this.singleUserStateProvider - .get(userId, ACCESS_TOKEN_DISK) - .update((_) => encryptedAccessToken.encryptedString); + // Save the encrypted access token to disk + await this.singleUserStateProvider + .get(userId, ACCESS_TOKEN_DISK) + .update((_) => encryptedAccessToken.encryptedString); - // TODO: PM-6408 - https://bitwarden.atlassian.net/browse/PM-6408 - // 2024-02-20: Remove access token from memory so that we migrate to encrypt the access token over time. - // Remove this call to remove the access token from memory after 3 releases. - await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_MEMORY).update((_) => null); + // TODO: PM-6408 + // 2024-02-20: Remove access token from memory so that we migrate to encrypt the access token over time. + // Remove this call to remove the access token from memory after 3 months. + await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_MEMORY).update((_) => null); + } catch (error) { + this.logService.error( + `SetAccessToken: storing encrypted access token in secure storage failed. Falling back to disk storage.`, + error, + ); + + // Fall back to disk storage for unecrypted access token + await this.singleUserStateProvider + .get(userId, ACCESS_TOKEN_DISK) + .update((_) => accessToken); + } return; } @@ -376,11 +396,11 @@ export class TokenService implements TokenServiceAbstraction { await this.singleUserStateProvider.get(userId, ACCESS_TOKEN_MEMORY).update((_) => null); } - async getAccessToken(userId?: UserId): Promise { + async getAccessToken(userId?: UserId): Promise { userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); if (!userId) { - return undefined; + return null; } // Try to get the access token from memory @@ -399,10 +419,41 @@ export class TokenService implements TokenServiceAbstraction { } if (this.platformSupportsSecureStorage) { - const accessTokenKey = await this.getAccessTokenKey(userId); + let accessTokenKey: AccessTokenKey; + try { + accessTokenKey = await this.getAccessTokenKey(userId); + } catch (error) { + if (EncString.isSerializedEncString(accessTokenDisk)) { + this.logService.error( + "Access token key retrieval failed. Unable to decrypt encrypted access token. Logging user out.", + error, + ); + await this.logoutCallback("accessTokenUnableToBeDecrypted", userId); + return null; + } + + // If the access token key is not found, but the access token is unencrypted then + // this indicates that this is the pre-migration state where the access token + // was stored unencrypted on disk. We can return the access token as is. + // Note: this is likely to only be hit for linux users who don't + // have a secure storage provider configured. + return accessTokenDisk; + } if (!accessTokenKey) { - // We know this is an unencrypted access token because we don't have an access token key + if (EncString.isSerializedEncString(accessTokenDisk)) { + // The access token is encrypted but we don't have the key to decrypt it for + // whatever reason so we have to log the user out. + this.logService.error( + "Access token key not found to decrypt encrypted access token. Logging user out.", + ); + + await this.logoutCallback("accessTokenUnableToBeDecrypted", userId); + + return null; + } + + // We know this is an unencrypted access token return accessTokenDisk; } @@ -410,17 +461,18 @@ export class TokenService implements TokenServiceAbstraction { const encryptedAccessTokenEncString = new EncString(accessTokenDisk as EncryptedString); const decryptedAccessToken = await this.decryptAccessToken( + accessTokenKey, encryptedAccessTokenEncString, - userId, ); return decryptedAccessToken; } catch (error) { - // If an error occurs during decryption, return null for logout. + // If an error occurs during decryption, logout and then return null. // We don't try to recover here since we'd like to know // if access token and key are getting out of sync. - this.logService.error( - `Failed to decrypt access token: ${error?.message ?? "Unknown error."}`, - ); + this.logService.error(`Failed to decrypt access token`, error); + + await this.logoutCallback("accessTokenUnableToBeDecrypted", userId); + return null; } } @@ -456,21 +508,49 @@ export class TokenService implements TokenServiceAbstraction { ); switch (storageLocation) { - case TokenStorageLocation.SecureStorage: - await this.saveStringToSecureStorage( - userId, - this.refreshTokenSecureStorageKey, - refreshToken, - ); + case TokenStorageLocation.SecureStorage: { + try { + await this.saveStringToSecureStorage( + userId, + this.refreshTokenSecureStorageKey, + refreshToken, + ); - // TODO: PM-6408 - https://bitwarden.atlassian.net/browse/PM-6408 - // 2024-02-20: Remove refresh token from memory and disk so that we migrate to secure storage over time. - // Remove these 2 calls to remove the refresh token from memory and disk after 3 releases. - await this.singleUserStateProvider.get(userId, REFRESH_TOKEN_DISK).update((_) => null); - await this.singleUserStateProvider.get(userId, REFRESH_TOKEN_MEMORY).update((_) => null); + // Check if the refresh token was able to be saved to secure storage by reading it + // immediately after setting it. This is needed due to intermittent silent failures on Windows 10/11. + const refreshTokenSecureStorage = await this.getStringFromSecureStorage( + userId, + this.refreshTokenSecureStorageKey, + ); + + // Only throw if the refresh token was not saved to secure storage + // If we only check for a nullish value out of secure storage without considering the input value, + // then we would end up falling back to disk storage if the input value was null. + if (refreshToken !== null && !refreshTokenSecureStorage) { + throw new Error("Refresh token failed to save to secure storage."); + } + + // TODO: PM-6408 + // 2024-02-20: Remove refresh token from memory and disk so that we migrate to secure storage over time. + // Remove these 2 calls to remove the refresh token from memory and disk after 3 months. + await this.singleUserStateProvider.get(userId, REFRESH_TOKEN_DISK).update((_) => null); + await this.singleUserStateProvider.get(userId, REFRESH_TOKEN_MEMORY).update((_) => null); + } catch (error) { + // This case could be hit for both Linux users who don't have secure storage configured + // or for Windows users who have intermittent issues with secure storage. + this.logService.error( + `SetRefreshToken: storing refresh token in secure storage failed. Falling back to disk storage.`, + error, + ); + + // Fall back to disk storage for refresh token + await this.singleUserStateProvider + .get(userId, REFRESH_TOKEN_DISK) + .update((_) => refreshToken); + } return; - + } case TokenStorageLocation.Disk: await this.singleUserStateProvider .get(userId, REFRESH_TOKEN_DISK) @@ -485,11 +565,11 @@ export class TokenService implements TokenServiceAbstraction { } } - async getRefreshToken(userId?: UserId): Promise { + async getRefreshToken(userId?: UserId): Promise { userId ??= await firstValueFrom(this.activeUserIdGlobalState.state$); if (!userId) { - return undefined; + return null; } // pre-secure storage migration: @@ -507,17 +587,30 @@ export class TokenService implements TokenServiceAbstraction { const refreshTokenDisk = await this.getStateValueByUserIdAndKeyDef(userId, REFRESH_TOKEN_DISK); if (refreshTokenDisk != null) { + // This handles the scenario pre-secure storage migration where the refresh token was stored on disk. return refreshTokenDisk; } if (this.platformSupportsSecureStorage) { - const refreshTokenSecureStorage = await this.getStringFromSecureStorage( - userId, - this.refreshTokenSecureStorageKey, - ); + try { + const refreshTokenSecureStorage = await this.getStringFromSecureStorage( + userId, + this.refreshTokenSecureStorageKey, + ); - if (refreshTokenSecureStorage != null) { - return refreshTokenSecureStorage; + if (refreshTokenSecureStorage != null) { + return refreshTokenSecureStorage; + } + + this.logService.error( + "Refresh token not found in secure storage. Access token will fail to refresh upon expiration or manual refresh.", + ); + } catch (error) { + // This case will be hit for Linux users who don't have secure storage configured. + + this.logService.error(`Failed to retrieve refresh token from secure storage`, error); + + await this.logoutCallback("refreshTokenSecureStorageRetrievalFailure", userId); } } 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 7561023a277..85640519ec3 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 @@ -140,6 +140,10 @@ export class UserVerificationService implements UserVerificationServiceAbstracti * @param verification User-supplied verification data (OTP, MP, PIN, or biometrics) */ async verifyUser(verification: Verification): Promise { + if (verification == null) { + throw new Error("Verification is required."); + } + const [userId, email] = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => [a?.id, a?.email])), ); diff --git a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts index 063b3c370b0..117b318768e 100644 --- a/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billilng-api.service.abstraction.ts @@ -1,3 +1,9 @@ +import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request"; +import { TokenizedPaymentMethodRequest } from "@bitwarden/common/billing/models/request/tokenized-payment-method.request"; +import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; +import { PaymentInformationResponse } from "@bitwarden/common/billing/models/response/payment-information.response"; + import { SubscriptionCancellationRequest } from "../../billing/models/request/subscription-cancellation.request"; import { OrganizationBillingMetadataResponse } from "../../billing/models/response/organization-billing-metadata.response"; import { OrganizationBillingStatusResponse } from "../../billing/models/response/organization-billing-status.response"; @@ -13,23 +19,50 @@ export abstract class BillingApiServiceAbstraction { organizationId: string, request: SubscriptionCancellationRequest, ) => Promise; + cancelPremiumUserSubscription: (request: SubscriptionCancellationRequest) => Promise; + createClientOrganization: ( providerId: string, request: CreateClientOrganizationRequest, ) => Promise; + + createSetupIntent: (paymentMethodType: PaymentMethodType) => Promise; + getBillingStatus: (id: string) => Promise; + getOrganizationBillingMetadata: ( organizationId: string, ) => Promise; + getOrganizationSubscription: ( organizationId: string, ) => Promise; + getPlans: () => Promise>; + + getProviderPaymentInformation: (providerId: string) => Promise; + getProviderSubscription: (providerId: string) => Promise; + updateClientOrganization: ( providerId: string, organizationId: string, request: UpdateClientOrganizationRequest, ) => Promise; + + updateProviderPaymentMethod: ( + providerId: string, + request: TokenizedPaymentMethodRequest, + ) => Promise; + + updateProviderTaxInformation: ( + providerId: string, + request: ExpandedTaxInfoUpdateRequest, + ) => Promise; + + verifyProviderBankAccount: ( + providerId: string, + request: VerifyBankAccountRequest, + ) => Promise; } diff --git a/libs/common/src/billing/abstractions/index.ts b/libs/common/src/billing/abstractions/index.ts new file mode 100644 index 00000000000..08a7a28fd9c --- /dev/null +++ b/libs/common/src/billing/abstractions/index.ts @@ -0,0 +1,7 @@ +export * from "./account/billing-account-profile-state.service"; +export * from "./billilng-api.service.abstraction"; +export * from "./organization-billing.service"; +export * from "./payment-method-warnings-service.abstraction"; +export * from "./payment-processors/braintree.service.abstraction"; +export * from "./payment-processors/stripe.service.abstraction"; +export * from "./provider-billing.service.abstraction"; diff --git a/libs/common/src/billing/abstractions/payment-processors/braintree.service.abstraction.ts b/libs/common/src/billing/abstractions/payment-processors/braintree.service.abstraction.ts new file mode 100644 index 00000000000..9391ab25f54 --- /dev/null +++ b/libs/common/src/billing/abstractions/payment-processors/braintree.service.abstraction.ts @@ -0,0 +1,28 @@ +export abstract class BraintreeServiceAbstraction { + /** + * Utilizes the Braintree SDK to create a [Braintree drop-in]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html} instance attached to the container ID specified as part of the {@link loadBraintree} method. + */ + createDropin: () => void; + + /** + * Loads the Bitwarden dropin.js script in the element of the current page. + * This script attaches the Braintree SDK to the window. + * @param containerId - The ID of the HTML element where the Braintree drop-in will be loaded at. + * @param autoCreateDropin - Specifies whether the Braintree drop-in should be created when dropin.js loads. + */ + loadBraintree: (containerId: string, autoCreateDropin: boolean) => void; + + /** + * Invokes the Braintree [requestPaymentMethod]{@link https://braintree.github.io/braintree-web-drop-in/docs/current/Dropin.html#requestPaymentMethod} method + * in order to generate a payment method token using the active Braintree drop-in. + */ + requestPaymentMethod: () => Promise; + + /** + * Removes the following elements from the of the current page: + * - The Bitwarden dropin.js script + * - Any