diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ae7c2b023cb..d2f0c75b9f5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -187,7 +187,6 @@ "json5", "keytar", "libc", - "log", "lowdb", "mini-css-extract-plugin", "napi", @@ -216,6 +215,8 @@ "simplelog", "style-loader", "sysinfo", + "tracing", + "tracing-subscriber", "ts-node", "ts-loader", "tsconfig-paths-webpack-plugin", diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 39549c4580c..02160c89288 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -304,7 +304,6 @@ jobs: path: apps/desktop/dist/com.bitwarden.desktop.flatpak if-no-files-found: error - linux-arm64: name: Linux ARM64 Build # Note, before updating the ubuntu version of the workflow, ensure the snap base image @@ -338,14 +337,24 @@ jobs: - name: Set up environment run: | sudo apt-get update - sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder + sudo apt-get -y install pkg-config libxss-dev rpm musl-dev musl-tools flatpak flatpak-builder squashfs-tools ruby ruby-dev rubygems build-essential + sudo gem install --no-document fpm + + - name: Set up Snap + run: sudo snap install snapcraft --classic + + - name: Install snaps required by snapcraft in destructive mode + run: | + sudo snap install core22 + sudo snap install gtk-common-themes + sudo snap install gnome-3-28-1804 - name: Print environment run: | node --version npm --version snap --version - snapcraft --version || echo 'snapcraft unavailable' + snapcraft --version - name: Install Node dependencies run: npm ci @@ -403,8 +412,19 @@ jobs: fi - name: Build application + env: + # Snapcraft environment variables to bypass LXD requirement on ARM64 + SNAPCRAFT_BUILD_ENVIRONMENT: host + USE_SYSTEM_FPM: true run: npm run dist:lin:arm64 + - name: Upload .snap artifact + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: bitwarden_${{ env._PACKAGE_VERSION }}_arm64.snap + path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_arm64.snap + if-no-files-found: error + - name: Upload tar.gz artifact uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: @@ -412,14 +432,27 @@ jobs: path: apps/desktop/dist/bitwarden_desktop_arm64.tar.gz if-no-files-found: error + - name: Build flatpak + working-directory: apps/desktop + run: | + sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo + sudo npm run pack:lin:flatpak + + - name: Upload flatpak artifact + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: com.bitwarden.desktop-arm64.flatpak + path: apps/desktop/dist/com.bitwarden.desktop.flatpak + if-no-files-found: error + windows: name: Windows Build runs-on: windows-2022 needs: - setup permissions: - contents: read - id-token: write + contents: read + id-token: write defaults: run: shell: pwsh @@ -677,8 +710,8 @@ jobs: runs-on: windows-2022 needs: setup permissions: - contents: read - id-token: write + contents: read + id-token: write defaults: run: shell: pwsh @@ -905,15 +938,14 @@ jobs: path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml if-no-files-found: error - macos-build: name: MacOS Build runs-on: macos-13 needs: - setup permissions: - contents: read - id-token: write + contents: read + id-token: write env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} @@ -1117,7 +1149,6 @@ jobs: - name: Build application (dev) run: npm run build - browser-build: name: Browser Build needs: setup @@ -1129,7 +1160,6 @@ jobs: pull-requests: write id-token: write - macos-package-github: name: MacOS Package GitHub Release Assets runs-on: macos-13 @@ -1139,8 +1169,8 @@ jobs: - macos-build - setup permissions: - contents: read - id-token: write + contents: read + id-token: write env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} @@ -1390,7 +1420,6 @@ jobs: path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml if-no-files-found: error - macos-package-mas: name: MacOS Package Prod Release Asset runs-on: macos-13 @@ -1400,8 +1429,8 @@ jobs: - macos-build - setup permissions: - contents: read - id-token: write + contents: read + id-token: write env: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} _NODE_VERSION: ${{ needs.setup.outputs.node_version }} @@ -1731,9 +1760,9 @@ jobs: - macos-package-github - macos-package-mas permissions: - contents: write - pull-requests: write - id-token: write + contents: write + pull-requests: write + id-token: write runs-on: ubuntu-22.04 steps: - name: Check out repo @@ -1771,7 +1800,6 @@ jobs: upload_sources: true upload_translations: false - check-failures: name: Check for failures if: always() @@ -1787,8 +1815,8 @@ jobs: - macos-package-mas - crowdin-push permissions: - contents: read - id-token: write + contents: read + id-token: write steps: - name: Check if any job failed if: | @@ -1823,4 +1851,3 @@ jobs: SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} with: status: ${{ job.status }} - diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 21786339299..c14abd7cd86 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -119,9 +119,9 @@ jobs: run: cargo sort --workspace --check - name: Install cargo-deny - uses: taiki-e/install-action@v2 + uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45 with: - tool: cargo-deny + tool: cargo-deny@0.18.5 - name: Run cargo deny working-directory: ./apps/desktop/desktop_native diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 9239914aeff..c7bebe86d51 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -109,6 +109,8 @@ jobs: apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_amd64.snap, + apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.snap, + apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.tar.gz, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.AppImage, apps/desktop/artifacts/Bitwarden-Portable-${{ env.PKG_VERSION }}.exe, apps/desktop/artifacts/Bitwarden-Installer-${{ env.PKG_VERSION }}.exe, diff --git a/.github/workflows/sdk-breaking-change-check.yml b/.github/workflows/sdk-breaking-change-check.yml new file mode 100644 index 00000000000..49b91d2d1a1 --- /dev/null +++ b/.github/workflows/sdk-breaking-change-check.yml @@ -0,0 +1,167 @@ +# This workflow runs TypeScript compatibility checks when the SDK is updated. +# Triggered automatically by the SDK repository via repository_dispatch when SDK PRs are created/updated. +name: SDK Breaking Change Check +run-name: "SDK breaking change check (${{ github.event.client_payload.sdk_version }})" +on: + repository_dispatch: + types: [sdk-breaking-change-check] + +permissions: + contents: read + actions: read + id-token: write + +jobs: + type-check: + name: TypeScript compatibility check + runs-on: ubuntu-24.04 + timeout-minutes: 15 + env: + _SOURCE_REPO: ${{ github.event.client_payload.source_repo }} + _SDK_VERSION: ${{ github.event.client_payload.sdk_version }} + _ARTIFACTS_RUN_ID: ${{ github.event.client_payload.artifacts_info.run_id }} + _ARTIFACT_NAME: ${{ github.event.client_payload.artifacts_info.artifact_name }} + _CLIENT_LABEL: ${{ github.event.client_payload.client_label }} + + steps: + - name: Log in to Azure + uses: bitwarden/gh-actions/azure-login@main + with: + subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + tenant_id: ${{ secrets.AZURE_TENANT_ID }} + client_id: ${{ secrets.AZURE_CLIENT_ID }} + - name: Get Azure Key Vault secrets + id: get-kv-secrets + uses: bitwarden/gh-actions/get-keyvault-secrets@main + with: + keyvault: gh-org-bitwarden + secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" + + - name: Generate GH App token + uses: actions/create-github-app-token@30bf6253fa41bdc8d1501d202ad15287582246b4 # v2.0.3 + id: app-token + with: + app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} + private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + - name: Log out from Azure + uses: bitwarden/gh-actions/azure-logout@main + - name: Validate inputs + run: | + echo "🔍 Validating required client_payload fields..." + + if [ -z "${_SOURCE_REPO}" ] || [ -z "${_SDK_VERSION}" ] || [ -z "${_ARTIFACTS_RUN_ID}" ] || [ -z "${_ARTIFACT_NAME}" ]; then + echo "::error::Missing required client_payload fields" + echo "SOURCE_REPO: ${_SOURCE_REPO}" + echo "SDK_VERSION: ${_SDK_VERSION}" + echo "ARTIFACTS_RUN_ID: ${_ARTIFACTS_RUN_ID}" + echo "ARTIFACT_NAME: ${_ARTIFACT_NAME}" + echo "CLIENT_LABEL: ${_CLIENT_LABEL}" + exit 1 + fi + + echo "✅ All required payload fields are present" + - name: Check out clients repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Get Node Version + id: retrieve-node-version + run: | + NODE_NVMRC=$(cat .nvmrc) + NODE_VERSION=${NODE_NVMRC/v/''} + echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT" + + - name: Set up Node + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + with: + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + node-version: ${{ steps.retrieve-node-version.outputs.node_version }} + + - name: Install Node dependencies + run: | + echo "📦 Installing Node dependencies with retry logic..." + + RETRY_COUNT=0 + MAX_RETRIES=3 + while [ ${RETRY_COUNT} -lt ${MAX_RETRIES} ]; do + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo "🔄 npm ci attempt ${RETRY_COUNT} of ${MAX_RETRIES}..." + + if npm ci; then + echo "✅ npm ci successful" + break + else + echo "❌ npm ci attempt ${RETRY_COUNT} failed" + [ ${RETRY_COUNT} -lt ${MAX_RETRIES} ] && sleep 5 + fi + done + + if [ ${RETRY_COUNT} -eq ${MAX_RETRIES} ]; then + echo "::error::npm ci failed after ${MAX_RETRIES} attempts" + exit 1 + fi + + - name: Download SDK artifacts + uses: bitwarden/gh-actions/download-artifacts@main + with: + github_token: ${{ steps.app-token.outputs.token }} + workflow: build-wasm-internal.yml + workflow_conclusion: success + run_id: ${{ env._ARTIFACTS_RUN_ID }} + artifacts: ${{ env._ARTIFACT_NAME }} + repo: ${{ env._SOURCE_REPO }} + path: ./sdk-internal + if_no_artifact_found: fail + + - name: Override SDK using npm link + working-directory: ./ + run: | + echo "🔧 Setting up SDK override using npm link..." + echo "📊 SDK Version: ${_SDK_VERSION}" + echo "📦 Artifact Source: ${_SOURCE_REPO} run ${_ARTIFACTS_RUN_ID}" + + echo "📋 SDK package contents:" + ls -la ./sdk-internal/ + + echo "🔗 Creating npm link to SDK package..." + if ! npm link ./sdk-internal; then + echo "::error::Failed to link SDK package" + exit 1 + fi + + - name: Run TypeScript compatibility check + run: | + + echo "🔍 Running TypeScript type checking for ${_CLIENT_LABEL} client with SDK version: ${_SDK_VERSION}" + echo "🎯 Type checking command: npm run test:types" + + # Add GitHub Step Summary output + { + echo "## 📊 TypeScript Compatibility Check (${_CLIENT_LABEL})" + echo "- **Client**: ${_CLIENT_LABEL}" + echo "- **SDK Version**: ${_SDK_VERSION}" + echo "- **Source Repository**: ${_SOURCE_REPO}" + echo "- **Artifacts Run ID**: ${_ARTIFACTS_RUN_ID}" + echo "" + } >> "$GITHUB_STEP_SUMMARY" + + + TYPE_CHECK_START=$(date +%s) + + # Run type check with timeout - exit code determines gh run watch result + if timeout 10m npm run test:types; then + TYPE_CHECK_END=$(date +%s) + TYPE_CHECK_DURATION=$((TYPE_CHECK_END - TYPE_CHECK_START)) + echo "✅ TypeScript compilation successful for ${_CLIENT_LABEL} client (${TYPE_CHECK_DURATION}s)" + echo "✅ **Result**: TypeScript compilation successful" >> "$GITHUB_STEP_SUMMARY" + echo "No breaking changes detected in ${_CLIENT_LABEL} client for SDK version ${_SDK_VERSION}" >> "$GITHUB_STEP_SUMMARY" + else + TYPE_CHECK_END=$(date +%s) + TYPE_CHECK_DURATION=$((TYPE_CHECK_END - TYPE_CHECK_START)) + echo "❌ TypeScript compilation failed for ${_CLIENT_LABEL} client after ${TYPE_CHECK_DURATION}s - breaking changes detected" + echo "❌ **Result**: TypeScript compilation failed" >> "$GITHUB_STEP_SUMMARY" + echo "Breaking changes detected in ${_CLIENT_LABEL} client for SDK version ${_SDK_VERSION}" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi \ No newline at end of file diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 00000000000..85b8b839182 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 2480eef505d..0b14f9d7444 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -4,6 +4,7 @@ import { componentWrapperDecorator } from "@storybook/angular"; import type { Preview } from "@storybook/angular"; import docJson from "../documentation.json"; + setCompodocJson(docJson); const wrapperDecorator = componentWrapperDecorator((story) => { diff --git a/apps/browser/package.json b/apps/browser/package.json index 744b53688b2..82d2ad7ab7a 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.10.1", + "version": "2025.11.0", "scripts": { "build": "npm run build:chrome", "build:bit": "npm run build:bit:chrome", diff --git a/apps/browser/spec/mock-port.spec-util.ts b/apps/browser/spec/mock-port.spec-util.ts index b5f7825d8e9..39239ba8817 100644 --- a/apps/browser/spec/mock-port.spec-util.ts +++ b/apps/browser/spec/mock-port.spec-util.ts @@ -12,6 +12,13 @@ export function mockPorts() { (chrome.runtime.connect as jest.Mock).mockImplementation((portInfo) => { const port = mockDeep(); port.name = portInfo.name; + port.sender = { url: chrome.runtime.getURL("") }; + + // convert to internal port + delete (port as any).tab; + delete (port as any).documentId; + delete (port as any).documentLifecycle; + delete (port as any).frameId; // set message broadcast (port.postMessage as jest.Mock).mockImplementation((message) => { diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index ad36ba5854a..053fb3b101f 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "اسأل عن القياسات الحيوية عند الإطلاق" }, - "premiumRequired": { - "message": "حساب البريميوم مطلوب" - }, - "premiumRequiredDesc": { - "message": "هذه المِيزة متاحة فقط للعضوية المميزة." - }, "authenticationTimeout": { "message": "مهلة المصادقة" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "يجب عليك إضافة رابط الخادم الأساسي أو على الأقل بيئة مخصصة." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "بيئة مخصصة" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 9a0239d2a34..68ea40b6808 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Açılışda biometrik soruşulsun" }, - "premiumRequired": { - "message": "Premium üzvlük lazımdır" - }, - "premiumRequiredDesc": { - "message": "Bu özəlliyi istifadə etmək üçün premium üzvlük lazımdır." - }, "authenticationTimeout": { "message": "Kimlik doğrulama vaxtı bitdi" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-lər, HTTPS istifadə etməlidir." + }, "customEnvironment": { "message": "Özəl mühit" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "İlkin ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ ilə uyuşma aşkarlamasını göstər", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Riskli girişlərinizi güvənli hala gətirməyiniz əladır!" }, + "upgradeNow": { + "message": "İndi yüksəlt" + }, + "builtInAuthenticator": { + "message": "Daxili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvənli fayl anbarı" + }, + "emergencyAccess": { + "message": "Fövqəladə hal erişimi" + }, + "breachMonitoring": { + "message": "Pozuntu monitorinqi" + }, + "andMoreFeatures": { + "message": "Və daha çoxu!" + }, + "planDescPremium": { + "message": "Tam onlayn təhlükəsizlik" + }, + "upgradeToPremium": { + "message": "\"Premium\"a yüksəlt" + }, "settingDisabledByPolicy": { "message": "Bu ayar, təşkilatınızın siyasəti tərəfindən sıradan çıxarılıb.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 35aaddc13b2..b0735109b41 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Пытацца пра біяметрыю пры запуску" }, - "premiumRequired": { - "message": "Патрабуецца прэміяльны статус" - }, - "premiumRequiredDesc": { - "message": "Для выкарыстання гэтай функцыі патрабуецца прэміяльны статус." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 68b962837eb..30386fe625e 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Питане за биометрични данни при пускане" }, - "premiumRequired": { - "message": "Изисква се платен абонамент" - }, - "premiumRequiredDesc": { - "message": "За да се възползвате от тази възможност, трябва да ползвате платен абонамент." - }, "authenticationTimeout": { "message": "Време на давност за удостоверяването" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." }, + "selfHostedEnvMustUseHttps": { + "message": "Адресите трябва да ползват HTTPS." + }, "customEnvironment": { "message": "Специална среда" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "По подразбиране ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показване на откритото съвпадение $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Добра работа с подсигуряването на данните за вписване в риск!" }, + "upgradeNow": { + "message": "Надграждане сега" + }, + "builtInAuthenticator": { + "message": "Вграден удостоверител" + }, + "secureFileStorage": { + "message": "Сигурно съхранение на файлове" + }, + "emergencyAccess": { + "message": "Авариен достъп" + }, + "breachMonitoring": { + "message": "Наблюдение за пробиви" + }, + "andMoreFeatures": { + "message": "И още!" + }, + "planDescPremium": { + "message": "Пълна сигурност в Интернет" + }, + "upgradeToPremium": { + "message": "Надградете до Платения план" + }, "settingDisabledByPolicy": { "message": "Тази настройка е изключена съгласно политиката на организацията Ви.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 25e37c06745..d68d19b0a05 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "প্রিমিয়াম আবশ্যক" - }, - "premiumRequiredDesc": { - "message": "এই বৈশিষ্ট্যটি ব্যবহার করতে একটি প্রিমিয়াম সদস্যতার প্রয়োজন।" - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 566f0e7077e..74f47fac7df 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index a5e00afae0c..824f37f069e 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Demaneu dades biometriques en iniciar" }, - "premiumRequired": { - "message": "Premium requerit" - }, - "premiumRequiredDesc": { - "message": "Cal una subscripció premium per utilitzar aquesta característica." - }, "authenticationTimeout": { "message": "Temps d'espera d'autenticació" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorn personalitzat" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 5dd4a6a6efc..46f5f414a1a 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ověřit biometrické údaje při spuštění" }, - "premiumRequired": { - "message": "Je vyžadováno členství Premium" - }, - "premiumRequiredDesc": { - "message": "Pro použití této funkce je potřebné členství Premium." - }, "authenticationTimeout": { "message": "Časový limit ověření" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." }, + "selfHostedEnvMustUseHttps": { + "message": "URL adresy musí používat HTTPS." + }, "customEnvironment": { "message": "Vlastní prostředí" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Výchozí ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Zobrazit detekci shody $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Skvělá práce při zabezpečení přihlašovacích údajů v ohrožení!" }, + "upgradeNow": { + "message": "Aktualizovat nyní" + }, + "builtInAuthenticator": { + "message": "Vestavěný autentifikátor" + }, + "secureFileStorage": { + "message": "Zabezpečené úložiště souborů" + }, + "emergencyAccess": { + "message": "Nouzový přístup" + }, + "breachMonitoring": { + "message": "Sledování úniků" + }, + "andMoreFeatures": { + "message": "A ještě více!" + }, + "planDescPremium": { + "message": "Dokončit online zabezpečení" + }, + "upgradeToPremium": { + "message": "Aktualizovat na Premium" + }, "settingDisabledByPolicy": { "message": "Toto nastavení je zakázáno zásadami Vaší organizace.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 1f46f034f5e..07c5a68e3ec 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Mae angen aelodaeth uwch" - }, - "premiumRequiredDesc": { - "message": "Mae angen aelodaeth uwch i ddefnyddio'r nodwedd hon." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Amgylchedd addasedig" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 7e1f66478cf..93b311e158b 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Bed om biometri ved start" }, - "premiumRequired": { - "message": "Premium påkrævet" - }, - "premiumRequiredDesc": { - "message": "Premium-medlemskab kræves for at anvende denne funktion." - }, "authenticationTimeout": { "message": "Godkendelsestimeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Der skal tilføjes enten basis server-URL'en eller mindst ét tilpasset miljø." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Brugerdefineret miljø" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Vis matchdetektion $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 9527c15e6a3..d88c396bb80 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -32,7 +32,7 @@ "message": "Single Sign-on verwenden" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Deine Organisation erfordert Single Sign-On." }, "welcomeBack": { "message": "Willkommen zurück" @@ -592,7 +592,7 @@ "message": "Anzeigen" }, "viewAll": { - "message": "View all" + "message": "Alles anzeigen" }, "viewLogin": { "message": "Zugangsdaten anzeigen" @@ -1035,10 +1035,10 @@ "message": "Eintrag gespeichert" }, "savedWebsite": { - "message": "Saved website" + "message": "Website gespeichert" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Gespeicherte Websites ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Beim Start nach biometrischen Daten fragen" }, - "premiumRequired": { - "message": "Premium-Mitgliedschaft benötigt" - }, - "premiumRequiredDesc": { - "message": "Eine Premium-Mitgliedschaft ist für diese Funktion notwendig." - }, "authenticationTimeout": { "message": "Authentifizierungs-Timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs müssen HTTPS verwenden." + }, "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, @@ -1695,28 +1692,28 @@ "message": "Auto-Ausfüllen deaktivieren" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Auto-Ausfüllen bestätigen" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Diese Website stimmt nicht mit deinen gespeicherten Zugangsdaten überein. Bevor du deine Zugangsdaten eingibst, stelle sicher, dass es sich um eine vertrauenswürdige Website handelt." }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Wie schützt Bitwarden deine Daten vor Phishing?" }, "currentWebsite": { - "message": "Current website" + "message": "Aktuelle Website" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Auto-Ausfüllen und diese Website hinzufügen" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Auto-Ausfüllen ohne Hinzufügen" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Nicht automatisch ausfüllen" }, "showInlineMenuIdentitiesLabel": { "message": "Identitäten als Vorschläge anzeigen" @@ -3280,7 +3277,7 @@ "message": "Entschlüsselungsfehler" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Fehler beim Abrufen der Auto-Ausfüllen-Daten" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." @@ -4054,10 +4051,10 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Kein Auto-Ausfüllen möglich" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Die Standard-Übereinstimmungserkennung steht auf \"Exakte Übereinstimmung\". Die aktuelle Website stimmt nicht genau mit den gespeicherten Zugangsdaten für diesen Eintrag überein." }, "okay": { "message": "Okay" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standard ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Übereinstimmungs-Erkennung anzeigen $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Gute Arbeit! Du hast deine gefährdeten Zugangsdaten geschützt!" }, + "upgradeNow": { + "message": "Jetzt upgraden" + }, + "builtInAuthenticator": { + "message": "Integrierter Authenticator" + }, + "secureFileStorage": { + "message": "Sicherer Dateispeicher" + }, + "emergencyAccess": { + "message": "Notfallzugriff" + }, + "breachMonitoring": { + "message": "Datenpannen-Überwachung" + }, + "andMoreFeatures": { + "message": "Und mehr!" + }, + "planDescPremium": { + "message": "Umfassende Online-Sicherheit" + }, + "upgradeToPremium": { + "message": "Upgrade auf Premium" + }, "settingDisabledByPolicy": { "message": "Diese Einstellung ist durch die Richtlinien deiner Organisation deaktiviert.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 230f5d60423..7fb60530511 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ζητήστε βιομετρικά κατά την εκκίνηση" }, - "premiumRequired": { - "message": "Απαιτείται το Premium" - }, - "premiumRequiredDesc": { - "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται συνδρομή Premium." - }, "authenticationTimeout": { "message": "Χρονικό όριο επαλήθευσης" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Πρέπει να προσθέσετε είτε το βασικό URL του διακομιστή ή τουλάχιστον ένα προσαρμοσμένο περιβάλλον." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Εμφάνιση ανιχνεύσεων αντιστοίχισης $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 50c629e87f6..a8743b0db68 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -4980,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5772,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 2058d68c55b..8ab541c569e 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 6c1b1e01139..68bf5497e37 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 1284563a6e3..060da79a4ff 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir datos biométricos al ejecutar" }, - "premiumRequired": { - "message": "Premium requerido" - }, - "premiumRequiredDesc": { - "message": "Una membrasía Premium es requerida para utilizar esta característica." - }, "authenticationTimeout": { "message": "Tiempo de autenticación agotado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes añadir la dirección URL del servidor base o al menos un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 3f163506214..acb440b2aa6 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Küsi avamisel biomeetriat" }, - "premiumRequired": { - "message": "Vajalik on Premium versioon" - }, - "premiumRequiredDesc": { - "message": "Selle funktsiooni kasutamiseks on vajalik tasulist kontot omada." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kohandatud keskkond" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index f74233193ef..016381e17f8 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Biometria eskatu saioa hastean" }, - "premiumRequired": { - "message": "Premium izatea beharrezkoa da" - }, - "premiumRequiredDesc": { - "message": "Premium bazkidetza beharrezkoa da ezaugarri hau erabiltzeko." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ingurune pertsonalizatua" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 6b52f1d4364..4f8529b2710 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "درخواست بیومتریک هنگام راه‌اندازی" }, - "premiumRequired": { - "message": "در نسخه پرمیوم کار می‌کند" - }, - "premiumRequiredDesc": { - "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." - }, "authenticationTimeout": { "message": "پایان زمان احراز هویت" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "محیط سفارشی" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "نمایش شناسایی تطابق برای $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index a0e6fce06bd..e3a5b44ea91 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -554,21 +554,21 @@ "message": "Nollaa haku" }, "archiveNoun": { - "message": "Archive", + "message": "Arkistoi", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arkistoi", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Poista arkistosta" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Arkistossa olevat kohteet" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Arkistossa ei ole kohteita" }, "noItemsInArchiveDesc": { "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." @@ -580,7 +580,7 @@ "message": "Item was unarchived" }, "archiveItem": { - "message": "Archive item" + "message": "Arkistoi kohde" }, "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" @@ -592,7 +592,7 @@ "message": "Näytä" }, "viewAll": { - "message": "View all" + "message": "Näytä kaikki" }, "viewLogin": { "message": "View login" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pyydä Biometristä todennusta käynnistettäessä" }, - "premiumRequired": { - "message": "Premium vaaditaan" - }, - "premiumRequiredDesc": { - "message": "Tämä ominaisuus edellyttää Premium-jäsenyyttä." - }, "authenticationTimeout": { "message": "Todennuksen aikakatkaisu" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Sinun on lisättävä joko palvelimen perusosoite tai ainakin yksi mukautettu palvelinympäristö." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, @@ -2009,11 +2006,11 @@ "message": "Muistiinpano" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "Uusi kirjautumistieto", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "Uusi kortti", "description": "Header for new card item type" }, "newItemHeaderIdentity": { @@ -2025,23 +2022,23 @@ "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "Uusi SSH-avain", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "Uusi teksti-Send", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "Uusi tiedosto-Send", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "Muokkaa kirjautumistietoa", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "Muokkaa korttia", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { @@ -2053,23 +2050,23 @@ "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "Muokkaa SSH avainta", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Muokkaa teksti-Sendiä", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Muokkaa tiedosto-Sendiä", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "Näytä kirjautumistieto", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "Näytä kortti", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { @@ -2081,7 +2078,7 @@ "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "Näytä SSH-avain", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Näytä vastaavuuden tunnistus $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 3c249b0a350..2b58095d950 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Mangyaring humingi ng mga biometrika sa paglunsad" }, - "premiumRequired": { - "message": "Premium na kinakailangan" - }, - "premiumRequiredDesc": { - "message": "Ang Premium na membership ay kinakailangan upang gamitin ang tampok na ito." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kapaligirang Custom" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index e3a153fe34f..afb58afcc25 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Demander la biométrie au lancement" }, - "premiumRequired": { - "message": "Premium requis" - }, - "premiumRequiredDesc": { - "message": "Une adhésion Premium est requise pour utiliser cette fonctionnalité." - }, "authenticationTimeout": { "message": "Délai d'authentification dépassé" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Vous devez ajouter soit l'URL du serveur de base, soit au moins un environnement personnalisé." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Environnement personnalisé" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Afficher la détection de correspondance $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Excellent travail pour sécuriser vos identifiants à risque !" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "Ce paramètre est désactivé par la politique de sécurité de votre organisation.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 6a13ce033b1..6d0410f112c 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Requirir biometría no inicio" }, - "premiumRequired": { - "message": "Plan Prémium requirido" - }, - "premiumRequiredDesc": { - "message": "Requírese un plan Prémium para poder empregar esta función." - }, "authenticationTimeout": { "message": "Tempo límite de autenticación superado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes engadir ou a URL base do servidor ou, polo menos, un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar detección de coincidencia $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 3834745f8e9..cc78e1a154a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -32,7 +32,7 @@ "message": "השתמש בכניסה יחידה" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "הארגון שלך דורש כניסה יחידה." }, "welcomeBack": { "message": "ברוך שובך" @@ -554,36 +554,36 @@ "message": "אפס חיפוש" }, "archiveNoun": { - "message": "Archive", + "message": "ארכיון", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "העבר לארכיון", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "הסר מהארכיון" }, "itemsInArchive": { - "message": "Items in archive" + "message": "פריטים בארכיון" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "אין פריטים בארכיון" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "הפריט נשלח לארכיון" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "הפריט הוסר מהארכיון" }, "archiveItem": { - "message": "Archive item" + "message": "העבר פריט לארכיון" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריט זה לארכיון?" }, "edit": { "message": "ערוך" @@ -592,7 +592,7 @@ "message": "הצג" }, "viewAll": { - "message": "View all" + "message": "הצג הכל" }, "viewLogin": { "message": "הצג כניסה" @@ -740,7 +740,7 @@ "message": "סיסמה ראשית שגויה" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "סיסמה ראשית אינה תקינה. יש לאשר שהדוא\"ל שלך נכון ושהחשבון שלך נוצר ב־$HOST$.", "placeholders": { "host": { "content": "$1", @@ -1035,10 +1035,10 @@ "message": "הפריט נשמר" }, "savedWebsite": { - "message": "Saved website" + "message": "אתר אינטרנט שנשמר" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "אתרי אינטרנט שנשמרו ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "בקש זיהוי ביומטרי בפתיחה" }, - "premiumRequired": { - "message": "נדרש פרימיום" - }, - "premiumRequiredDesc": { - "message": "נדרשת חברות פרימיום כדי להשתמש בתכונה זו." - }, "authenticationTimeout": { "message": "פסק זמן לאימות" }, @@ -1567,13 +1561,13 @@ "message": "קרא מפתח אבטחה" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "קורא מפתח גישה..." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "אימות מפתח גישה נכשל" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "השתמש בשיטת כניסה אחרת" }, "awaitingSecurityKeyInteraction": { "message": "ממתין לאינטראקציה עם מפתח אבטחה..." @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, + "selfHostedEnvMustUseHttps": { + "message": "כתובות URL מוכרחות להשתמש ב־HTTPS." + }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, @@ -1695,28 +1692,28 @@ "message": "השבת מילוי אוטומטי" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "אשר מילוי אוטומטי" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "אתר זה אינו תואם את פרטי הכניסה השמורה שלך. לפני שאתה ממלא את אישורי הכניסה שלך, וודא שזהו אתר מהימן." }, "showInlineMenuLabel": { "message": "הצג הצעות למילוי אוטומטי על שדות טופס" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "כיצד Bitwarden מגנה על הנתונים שלך מדיוג?" }, "currentWebsite": { - "message": "Current website" + "message": "אתר נוכחי" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "מלא אוטומטית והוסף אתר אינטרנט זה" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "מלא אוטומטית מבלי להוסיף" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "אל תמלא אוטומטית" }, "showInlineMenuIdentitiesLabel": { "message": "הצג זהויות כהצעות" @@ -2009,79 +2006,79 @@ "message": "הערה" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "כניסה חדשה", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "כרטיס חדש", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "זהות חדשה", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "הערה חדשה", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "מפתח SSH חדש", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "סֵנְד של טקסט חדש", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "סֵנְד של קובץ חדש", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "ערוך כניסה", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "ערוך כרטיס", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "ערוך זהות", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "ערוך הערה", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "ערוך מפתח SSH", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "ערוך סֵנְד של טקסט", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "ערוך סֵנְד של קובץ", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "הצג כניסה", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "הצג כרטיס", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "הצג זהות", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "הצג הערה", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "הצג מפתח SSH", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -3256,7 +3253,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא.", "placeholders": { "organization": { "content": "$1", @@ -3265,7 +3262,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא. פריטי האוספים שלי לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -3280,7 +3277,7 @@ "message": "שגיאת פענוח" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "שגיאה בקבלת נתוני מילוי אוטומטי" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden לא יכל לפענח את פריט(י) הכספת המפורט(ים) להלן." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "לא ניתן למלא אוטומטית" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "ברירת המחדל להתאמה מוגדרת כ'התאמה מדויקת'. האתר הנוכחי אינו תואם באופן מדויק את פרטי הכניסה השמורים עבור פריט זה." }, "okay": { - "message": "Okay" + "message": "בסדר" }, "toggleSideNavigation": { "message": "החלף מצב ניווט צדדי" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "הצג זיהוי התאמה $WEBSITE$", "placeholders": { @@ -5586,7 +5593,7 @@ "message": "אפשרויות כספת" }, "emptyVaultDescription": { - "message": "הכספת מגינה על יותר מרק הסיסמאות שלך. אחסן כניסות מאובטחות, זהויות, כרטיסים והערות באופן מאובטח כאן." + "message": "הכספת מגנה על יותר מרק הסיסמאות שלך. אחסן כניסות מאובטחות, זהויות, כרטיסים והערות באופן מאובטח כאן." }, "introCarouselLabel": { "message": "ברוך בואך אל Bitwarden" @@ -5631,30 +5638,30 @@ "message": "ברוך בואך אל הכספת שלך!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "זוהה ניסיון דיוג" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "האתר שאתה מנסה לבקר הוא אתר זדוני ידוע וסכנת אבטחה." }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "סגור כרטיסיה זו" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "המשך לאתר זה (לא מומלץ)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "אתר זה נמצא ב־", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ", רשימת קוד פתוח של אתרי דיוג ידועים המשמשים לגניבת מידע אישי ורגיש.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "למד עוד על זיהוי דיוג" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "מוגן על ידי $PRODUCT$", "placeholders": { "product": { "content": "$1", @@ -5767,16 +5774,40 @@ "message": "אשר דומיין של Key Connector" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "עבודה נהדרת באבטחת הכניסות בסיכון שלך!" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "הגדרה זו מושבתת על ידי מדיניות של הארגון שלך.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "מיקוד" }, "cardNumberLabel": { - "message": "Card number" + "message": "מספר כרטיס" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 3172e767974..c27fa6f7eb7 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "लॉन्च पर बायोमेट्रिक्स के लिए पूछें" }, - "premiumRequired": { - "message": "Premium Required" - }, - "premiumRequiredDesc": { - "message": "इस सुविधा का उपयोग करने के लिए प्रीमियम सदस्यता की आवश्यकता होती है।" - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 9e4c8d34004..9d7539a9bd5 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -32,7 +32,7 @@ "message": "Jedinstvena prijava (SSO)" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tvoja organizacija zahtijeva jedinstvenu prijavu." }, "welcomeBack": { "message": "Dobro došli natrag" @@ -592,7 +592,7 @@ "message": "Prikaz" }, "viewAll": { - "message": "View all" + "message": "Vidi sve" }, "viewLogin": { "message": "Prikaži prijavu" @@ -1035,10 +1035,10 @@ "message": "Stavka izmijenjena" }, "savedWebsite": { - "message": "Saved website" + "message": "Spremljeno mrežno mjesto" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Spremljena mrežna mjesta ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Traži biometrijsku autentifikaciju pri pokretanju" }, - "premiumRequired": { - "message": "Potrebno premium članstvo" - }, - "premiumRequiredDesc": { - "message": "Za korištenje ove značajke potrebno je Premium članstvo." - }, "authenticationTimeout": { "message": "Istek vremena za autentifikaciju" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Moraš dodati ili osnovni URL poslužitelja ili barem jedno prilagođeno okruženje." }, + "selfHostedEnvMustUseHttps": { + "message": "URL mora koristiti HTTPS." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -1695,28 +1692,28 @@ "message": "Isključi auto-ispunu" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Potvrdi auto-ispunu" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Ova stranica ne odgovara tvojim spremljenim podacima za prijavu. Prije nego što uneseš svoje podatke za prijavu, provjeri je li riječ o pouzdanoj stranici." }, "showInlineMenuLabel": { "message": "Prikaži prijedloge auto-ispune na poljima obrazaca" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Kako Bitwarden štiti tvoje podatke od phishinga?" }, "currentWebsite": { - "message": "Current website" + "message": "Trenutna web stranica" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Auto-ispuni i dodaj ovu stranicu" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Auto-ispuni bez dodavanja" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Nemoj auto-ispuniti" }, "showInlineMenuIdentitiesLabel": { "message": "Prikaži identitete kao prijedloge" @@ -3280,7 +3277,7 @@ "message": "Pogreška pri dešifriranju" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Greška kod dohvata podataka za auto-ispunu" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden nije mogao dešifrirati sljedeće stavke trezora." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Nije moguća auto-ispuna" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Zadano podudaranje postavljeno je na „Točno podudaranje”. Trenutna web-stranica ne podudara se točno sa spremljenim podacima ove stavke za prijavu." }, "okay": { - "message": "Okay" + "message": "U redu" }, "toggleSideNavigation": { "message": "U/Isključi bočnu navigaciju" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Zadano ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Prikaži otkrivanje podudaranja $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Rizične prijave su osigurane!" }, + "upgradeNow": { + "message": "Nadogradi sada" + }, + "builtInAuthenticator": { + "message": "Ugrađeni autentifikator" + }, + "secureFileStorage": { + "message": "Sigurna pohrana datoteka" + }, + "emergencyAccess": { + "message": "Pristup u nuždi" + }, + "breachMonitoring": { + "message": "Nadzor proboja" + }, + "andMoreFeatures": { + "message": "I više!" + }, + "planDescPremium": { + "message": "Dovrši online sigurnost" + }, + "upgradeToPremium": { + "message": " Nadogradi na Premium" + }, "settingDisabledByPolicy": { "message": "Ova je postavka onemogućena pravilima tvoje organizacije.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Poštanski broj" }, "cardNumberLabel": { - "message": "Card number" + "message": "Broj kartice" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index a84487e5a1d..9f10494258a 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -32,7 +32,7 @@ "message": "Egyszeri bejelentkezés használata" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A szervezet egyszeri bejelentkezést igényel." }, "welcomeBack": { "message": "Üdvözlet újra" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Biometria kérése indításkor" }, - "premiumRequired": { - "message": "Prémium funkció szükséges" - }, - "premiumRequiredDesc": { - "message": "Prémium tagság szükséges ennek a funkciónak eléréséhez a jövőben." - }, "authenticationTimeout": { "message": "Hitelesítési időkifutás" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." }, + "selfHostedEnvMustUseHttps": { + "message": "A webcímeknek HTTPS-t kell használniuk." + }, "customEnvironment": { "message": "Egyedi környezet" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ egyező érzékelés megjelenítése", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Remek munka a kockázatos bejelentkezések biztosítása!" }, + "upgradeNow": { + "message": "Áttérés most" + }, + "builtInAuthenticator": { + "message": "Beépített hitelesítő" + }, + "secureFileStorage": { + "message": "Biztonságos fájl tárolás" + }, + "emergencyAccess": { + "message": "Sürgősségi hozzáférés" + }, + "breachMonitoring": { + "message": "Adatszivárgás figyelés" + }, + "andMoreFeatures": { + "message": "És még több!" + }, + "planDescPremium": { + "message": "Teljes körű online biztonság" + }, + "upgradeToPremium": { + "message": "Áttérés Prémium csomagra" + }, "settingDisabledByPolicy": { "message": "Ezt a beállítást a szervezet házirendje letiltotta.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index a94709a1be1..26a2b8dc6bd 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Tanyakan untuk biometrik pada saat diluncurkan" }, - "premiumRequired": { - "message": "Membutuhkan Keanggotaan Premium" - }, - "premiumRequiredDesc": { - "message": "Keanggotaan premium diperlukan untuk menggunakan fitur ini." - }, "authenticationTimeout": { "message": "Batas waktu otentikasi" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Anda harus menambahkan antara URL dasar server atau paling tidak satu lingkungan ubahsuai." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Lingkungan Khusus" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Tampilkan deteksi kecocokan $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 05cd6937246..60c97d7157a 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Richiedi dati biometrici all'avvio" }, - "premiumRequired": { - "message": "Premium necessario" - }, - "premiumRequiredDesc": { - "message": "Passa a Premium per utilizzare questa funzionalità." - }, "authenticationTimeout": { "message": "Timeout autenticazione" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizzato" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostra corrispondenza $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "Questa impostazione è disabilitata dalle restrizioni della tua organizzazione.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 54405f69157..7c4420508a2 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "起動時に生体認証を要求する" }, - "premiumRequired": { - "message": "プレミアム会員専用" - }, - "premiumRequiredDesc": { - "message": "この機能を使うにはプレミアム会員になってください。" - }, "authenticationTimeout": { "message": "認証のタイムアウト" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "カスタム環境" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "一致検出 $WEBSITE$を表示", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 82f18caf79f..eaa5bc43021 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index f1c9e0ee8ab..e5adcfce833 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "ಪ್ರೀಮಿಯಂ ಅಗತ್ಯವಿದೆ" - }, - "premiumRequiredDesc": { - "message": "ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು ಪ್ರೀಮಿಯಂ ಸದಸ್ಯತ್ವ ಅಗತ್ಯವಿದೆ." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index c5a414fc81f..6037d208b42 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "실행 시 생체 인증 요구하기" }, - "premiumRequired": { - "message": "프리미엄 멤버십 필요" - }, - "premiumRequiredDesc": { - "message": "이 기능을 사용하려면 프리미엄 멤버십이 필요합니다." - }, "authenticationTimeout": { "message": "인증 시간 초과" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "기본 서버 URL이나 최소한 하나의 사용자 지정 환경을 추가해야 합니다." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "사용자 지정 환경" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ 일치 인식 보이기", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index df55af589bf..8e858de4f47 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Paleidžiant patvirtinti biometrinius duomenis" }, - "premiumRequired": { - "message": "Premium reikalinga" - }, - "premiumRequiredDesc": { - "message": "Premium narystė reikalinga šiai funkcijai naudoti." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Individualizuota aplinka" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 6ca99492c50..a4be22d433a 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Palaižot vaicāt biometriju" }, - "premiumRequired": { - "message": "Nepieciešams Premium" - }, - "premiumRequiredDesc": { - "message": "Ir nepieciešama Premium dalība, lai izmantotu šo iespēju." - }, "authenticationTimeout": { "message": "Autentificēšanās noildze" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Jāpievieno vai no servera pamata URL vai vismaz viena pielāgota vide." }, + "selfHostedEnvMustUseHttps": { + "message": "URL ir jābūt HTTPS." + }, "customEnvironment": { "message": "Pielāgota vide" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Noklusējums ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Rādīt atbilstības noteikšanu $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Labs darbs riskam pakļauto pieteikšanās vienumu drošības uzlabošanā!" }, + "upgradeNow": { + "message": "Uzlabot tagad" + }, + "builtInAuthenticator": { + "message": "Iebūvēts autentificētājs" + }, + "secureFileStorage": { + "message": "Droša datņu krātuve" + }, + "emergencyAccess": { + "message": "Ārkārtas piekļuve" + }, + "breachMonitoring": { + "message": "Noplūžu pārraudzīšana" + }, + "andMoreFeatures": { + "message": "Un vēl!" + }, + "planDescPremium": { + "message": "Pilnīga drošība tiešsaistē" + }, + "upgradeToPremium": { + "message": "Uzlabot uz Premium" + }, "settingDisabledByPolicy": { "message": "Šis iestatījums ir atspējots apvienības pamatnostādnēs.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 75eeb54c176..cda9ec03923 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "പ്രീമിയം അംഗത്വം ആവശ്യമാണ്" - }, - "premiumRequiredDesc": { - "message": "ഈ സവിശേഷത ഉപയോഗിക്കുന്നതിന് പ്രീമിയം അംഗത്വം ആവശ്യമാണ്." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 333dda2a2f8..57624a82381 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 3d632a60d3c..1268c960c8f 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Spør om biometri ved oppstart" }, - "premiumRequired": { - "message": "Premium er påkrevd" - }, - "premiumRequiredDesc": { - "message": "Et Premium-medlemskap er påkrevd for å bruke denne funksjonen." - }, "authenticationTimeout": { "message": "Tidsavbrudd for autentisering" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d413149bd18..441ea71d840 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Vraag om biometrie bij opstarten" }, - "premiumRequired": { - "message": "Premium is vereist" - }, - "premiumRequiredDesc": { - "message": "Je hebt een Premium-abonnement nodig om deze functie te gebruiken." - }, "authenticationTimeout": { "message": "Authenticatie-timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, + "selfHostedEnvMustUseHttps": { + "message": "URL's moeten HTTPS gebruiken." + }, "customEnvironment": { "message": "Aangepaste omgeving" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standaard ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Overeenkomstdetectie weergeven $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Goed gedaan, je hebt je risicovolle inloggegevens verbeterd!" }, + "upgradeNow": { + "message": "Nu upgraden" + }, + "builtInAuthenticator": { + "message": "Ingebouwde authenticator" + }, + "secureFileStorage": { + "message": "Beveiligde bestandsopslag" + }, + "emergencyAccess": { + "message": "Noodtoegang" + }, + "breachMonitoring": { + "message": "Lek-monitoring" + }, + "andMoreFeatures": { + "message": "En meer!" + }, + "planDescPremium": { + "message": "Online beveiliging voltooien" + }, + "upgradeToPremium": { + "message": "Opwaarderen naar Premium" + }, "settingDisabledByPolicy": { "message": "Deze instelling is uitgeschakeld door het beleid van uw organisatie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 6c9bea95451..1047ac9466e 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Wymagaj odblokowania biometrią po uruchomieniu przeglądarki" }, - "premiumRequired": { - "message": "Konto premium jest wymagane" - }, - "premiumRequiredDesc": { - "message": "Konto premium jest wymagane, aby skorzystać z tej funkcji." - }, "authenticationTimeout": { "message": "Przekroczono limit czasu uwierzytelniania" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Niestandardowe środowisko" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Pokaż wykrywanie dopasowania $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 1496455e85b..a4da9025a8e 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -32,7 +32,7 @@ "message": "Usar autenticação única" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A sua organização requer o uso da autenticação única." }, "welcomeBack": { "message": "Boas-vindas de volta" @@ -59,13 +59,13 @@ "message": "Endereço de e-mail" }, "masterPass": { - "message": "Senha mestra" + "message": "Senha principal" }, "masterPassDesc": { - "message": "A senha mestra é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha mestra. Não há maneira de recuperar a senha caso você se esqueça." + "message": "A senha principal é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha principal. Não há maneira de recuperar a senha caso você se esqueça." }, "masterPassHintDesc": { - "message": "Uma dica de senha mestra pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." + "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." }, "masterPassHintText": { "message": "Se você esquecer sua senha, a dica de senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", @@ -81,10 +81,10 @@ } }, "reTypeMasterPass": { - "message": "Digite novamente a senha mestra" + "message": "Digite novamente a senha principal" }, "masterPassHint": { - "message": "Dica de Senha Mestra (opcional)" + "message": "Dica de senha principal (opcional)" }, "passwordStrengthScore": { "message": "Pontuação de força da senha $SCORE$", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se nessa organização definindo uma senha mestra." + "message": "Termine de juntar-se à organização definindo uma senha principal." }, "tab": { "message": "Aba" @@ -264,13 +264,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha mestra" + "message": "Dica da senha principal" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e dica da sua senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha mestra" + "message": "Obter dica da senha principal" }, "continue": { "message": "Continuar" @@ -291,7 +291,7 @@ "message": "Confirme a sua identidade para continuar." }, "changeMasterPassword": { - "message": "Alterar senha mestra" + "message": "Alterar senha principal" }, "continueToWebApp": { "message": "Continuar no aplicativo web?" @@ -312,7 +312,7 @@ "message": "Ajude outras pessoas a descobrirem se o Bitwarden é o que elas estão procurando. Visite a loja de extensões do seu navegador e deixe uma avaliação agora." }, "changeMasterPasswordOnWebConfirmation": { - "message": "Você pode alterar a sua senha mestra no aplicativo web do Bitwarden." + "message": "Você pode alterar a sua senha principal no aplicativo web do Bitwarden." }, "fingerprintPhrase": { "message": "Frase biométrica", @@ -592,7 +592,7 @@ "message": "Ver" }, "viewAll": { - "message": "View all" + "message": "Ver tudo" }, "viewLogin": { "message": "Ver credencial" @@ -737,10 +737,10 @@ } }, "invalidMasterPassword": { - "message": "Senha mestra inválida" + "message": "Senha principal inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Senha mestre inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", + "message": "Senha principal inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -806,16 +806,16 @@ "message": "Segurança" }, "confirmMasterPassword": { - "message": "Confirme a senha mestra" + "message": "Confirme a senha principal" }, "masterPassword": { - "message": "Senha mestra" + "message": "Senha principal" }, "masterPassImportant": { - "message": "Sua senha mestra não pode ser recuperada se você esquecê-la!" + "message": "Sua senha principal não pode ser recuperada se você esquecê-la!" }, "masterPassHintLabel": { - "message": "Dica da senha mestra" + "message": "Dica da senha principal" }, "errorOccurred": { "message": "Ocorreu um erro" @@ -827,13 +827,13 @@ "message": "Endereço de e-mail inválido." }, "masterPasswordRequired": { - "message": "A senha mestre é necessária." + "message": "A senha principal é necessária." }, "confirmMasterPasswordRequired": { - "message": "É necessário digitar a senha mestra novamente." + "message": "É necessário digitar a senha principal novamente." }, "masterPasswordMinlength": { - "message": "A senha mestra deve ter pelo menos $VALUE$ caracteres.", + "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -843,7 +843,7 @@ } }, "masterPassDoesntMatch": { - "message": "A confirmação da senha mestra não corresponde." + "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." @@ -861,7 +861,7 @@ "message": "Você pode fechar esta janela" }, "masterPassSent": { - "message": "Enviamos um e-mail com a dica da sua senha mestra." + "message": "Enviamos um e-mail com a dica da sua senha principal." }, "verificationCodeRequired": { "message": "O código de verificação é necessário." @@ -1035,10 +1035,10 @@ "message": "Item salvo" }, "savedWebsite": { - "message": "Saved website" + "message": "Site salvo" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Sites salvos ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1242,7 +1242,7 @@ "message": "Ao alterar sua senha, você precisará entrar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectados dentro de uma hora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Mude a sua senha mestre para completar a recuperação de conta." + "message": "Mude a sua senha principal para completar a recuperação de conta." }, "enableChangedPasswordNotification": { "message": "Pedir para atualizar credencial existente" @@ -1326,7 +1326,7 @@ "message": "Esta senha será usada para exportar e importar este arquivo" }, "accountRestrictedOptionDescription": { - "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha mestra da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." + "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtectedOptionDescription": { "message": "Defina uma senha para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografar." @@ -1361,7 +1361,7 @@ "message": "As chaves de criptografia são únicas para cada conta de usuário do Bitwarden, então você não pode importar um arquivo de exportação criptografado para uma conta diferente." }, "exportMasterPassword": { - "message": "Insira a sua senha mestra para exportar os dados do seu cofre." + "message": "Insira a sua senha principal para exportar os dados do seu cofre." }, "shared": { "message": "Compartilhado" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir biometria ao abrir" }, - "premiumRequired": { - "message": "Requer Assinatura Premium" - }, - "premiumRequiredDesc": { - "message": "Uma assinatura Premium é necessária para usar esse recurso." - }, "authenticationTimeout": { "message": "Tempo de autenticação esgotado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs devem usar HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -1695,28 +1692,28 @@ "message": "Desativar o preenchimento automático" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Confirmar preenchimento automático" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Esse site não corresponde aos detalhes salvos na credencial. Antes de preencher suas credenciais de acesso, certifique-se de que é um site confiável." }, "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático em campos de formulário" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Como que o Bitwarden protege seus dados de phishing?" }, "currentWebsite": { - "message": "Current website" + "message": "Site atual" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Preencher automaticamente e adicionar este site" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Preencher automaticamente sem adicionar" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Não preencher automaticamente" }, "showInlineMenuIdentitiesLabel": { "message": "Exibir identidades como sugestões" @@ -2267,10 +2264,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Senha mestra fraca" + "message": "Senha principal Fraca" }, "weakMasterPasswordDesc": { - "message": "A senha mestra que você selecionou está fraca. Você deve usar uma senha mestra forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha mestra?" + "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" }, "pin": { "message": "PIN", @@ -2304,7 +2301,7 @@ "message": "Desbloquear com a biometria" }, "unlockWithMasterPassword": { - "message": "Desbloquear com senha mestra" + "message": "Desbloquear com senha principal" }, "awaitDesktop": { "message": "Aguardando confirmação do desktop" @@ -2313,10 +2310,10 @@ "message": "Confirme o uso de biometria no aplicativo do Bitwarden Desktop para ativar a biometria para o navegador." }, "lockWithMasterPassOnRestart": { - "message": "Bloquear com senha mestra ao reiniciar o navegador" + "message": "Bloquear com senha principal ao reiniciar o navegador" }, "lockWithMasterPassOnRestart1": { - "message": "Exigir senha mestra ao reiniciar o navegador" + "message": "Exigir senha principal ao reiniciar o navegador" }, "selectOneCollection": { "message": "Você deve selecionar pelo menos uma coleção." @@ -2431,19 +2428,19 @@ } }, "setMasterPassword": { - "message": "Definir senha mestra" + "message": "Definir senha principal" }, "currentMasterPass": { - "message": "Senha mestra atual" + "message": "Senha principal atual" }, "newMasterPass": { - "message": "Nova senha mestra" + "message": "Nova senha principal" }, "confirmNewMasterPass": { - "message": "Confirmar nova senha mestra" + "message": "Confirmar nova senha principal" }, "masterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a sua senha mestra cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a sua senha principal cumpra aos seguintes requisitos:" }, "policyInEffectMinComplexity": { "message": "Pontuação mínima de complexidade de $SCORE$", @@ -2482,7 +2479,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "A sua nova senha mestra não cumpre aos requisitos da política." + "message": "A sua nova senha principal não cumpre aos requisitos da política." }, "receiveMarketingEmailsV2": { "message": "Receba conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." @@ -2596,7 +2593,7 @@ "message": "Biometria falhou" }, "biometricsFailedDesc": { - "message": "A biometria não pode ser concluída, considere usar uma senha mestra ou desconectar. Se isso persistir, entre em contato com o suporte do Bitwarden." + "message": "A biometria não pode ser concluída, considere usar uma senha principal ou desconectar. Se isso persistir, entre em contato com o suporte do Bitwarden." }, "nativeMessaginPermissionErrorTitle": { "message": "Permissão não fornecida" @@ -3034,13 +3031,13 @@ "message": "Oculte seu endereço de e-mail dos visualizadores." }, "passwordPrompt": { - "message": "Solicitação nova de senha mestra" + "message": "Solicitação nova de senha principal" }, "passwordConfirmation": { - "message": "Confirmação de senha mestra" + "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha mestra para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." }, "emailVerificationRequired": { "message": "Verificação de e-mail necessária" @@ -3052,28 +3049,28 @@ "message": "Você precisa verificar o seu e-mail para usar este recurso. Você pode verificar seu e-mail no cofre web." }, "masterPasswordSuccessfullySet": { - "message": "Senha mestra definida com sucesso" + "message": "Senha principal definida com sucesso" }, "updatedMasterPassword": { - "message": "Senha mestra atualizada" + "message": "Senha principal atualizada" }, "updateMasterPassword": { - "message": "Atualizar senha mestra" + "message": "Atualizar senha principal" }, "updateMasterPasswordWarning": { - "message": "Sua senha mestra foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha mestra para acessar o seu cofre." + "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha principal para acessar o seu cofre." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscrição automática" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha mestra." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "selectFolder": { "message": "Selecionar pasta..." @@ -3083,11 +3080,11 @@ "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha mestra.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha mestra.", + "message": "Sua organização requer que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -3193,7 +3190,7 @@ "message": "Nenhum identificador exclusivo encontrado." }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha mestra não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." + "message": "Uma senha principal não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." }, "organizationName": { "message": "Nome da organização" @@ -3205,10 +3202,10 @@ "message": "Sair da organização" }, "removeMasterPassword": { - "message": "Remover senha mestra" + "message": "Remover senha principal" }, "removedMasterPassword": { - "message": "Senha mestra removida" + "message": "Senha principal removida" }, "leaveOrganizationConfirmation": { "message": "Você tem certeza que deseja sair desta organização?" @@ -3280,7 +3277,7 @@ "message": "Erro ao descriptografar" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Erro ao obter dados de preenchimento automático" }, "couldNotDecryptVaultItemsBelow": { "message": "O Bitwarden não conseguiu descriptografar o(s) item(ns) do cofre listado abaixo." @@ -3557,7 +3554,7 @@ } }, "loginWithMasterPassword": { - "message": "Entrar com a senha mestra" + "message": "Entrar com a senha principal" }, "newAroundHere": { "message": "Novo por aqui?" @@ -3630,16 +3627,16 @@ "message": "Estado de autenticação" }, "masterPasswordChanged": { - "message": "Senha mestre salva" + "message": "Senha principal salva" }, "exposedMasterPassword": { - "message": "Senha mestra comprometida" + "message": "Senha principal comprometida" }, "exposedMasterPasswordDesc": { "message": "A senha foi encontrada em um vazamento de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha já exposta?" }, "weakAndExposedMasterPassword": { - "message": "Senha mestra fraca e comprometida" + "message": "Senha principal fraca e comprometida" }, "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" @@ -3651,7 +3648,7 @@ "message": "Importante:" }, "masterPasswordHint": { - "message": "Sua senha mestra não pode ser recuperada se você a esquecer!" + "message": "Sua senha principal não pode ser recuperada se você a esquecer!" }, "characterMinimum": { "message": "Mínimo de $LENGTH$ caracteres", @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Não é possível preencher automaticamente" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "A correspondência padrão está configurada como 'Correspondência exata'. O site atual não corresponde exatamente aos detalhes salvos de credencial para este item." }, "okay": { - "message": "Okay" + "message": "Ok" }, "toggleSideNavigation": { "message": "Habilitar navegação lateral" @@ -4208,7 +4205,7 @@ "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha mestra" + "message": "Usar a senha principal" }, "usePin": { "message": "Usar PIN" @@ -4422,7 +4419,7 @@ "message": "Código" }, "lastPassMasterPassword": { - "message": "Senha mestra do LastPass" + "message": "Senha principal do LastPass" }, "lastPassAuthRequired": { "message": "Autenticação do LastPass necessária" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Padrão ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar detecção de correspondência $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Ótimo trabalho protegendo suas credenciais em risco!" }, + "upgradeNow": { + "message": "Faça upgrade agora" + }, + "builtInAuthenticator": { + "message": "Autenticador integrado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de arquivos" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitoramento de brechas" + }, + "andMoreFeatures": { + "message": "E mais!" + }, + "planDescPremium": { + "message": "Segurança on-line completa" + }, + "upgradeToPremium": { + "message": "Faça upgrade para o Premium" + }, "settingDisabledByPolicy": { "message": "Essa configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index d3acb309860..15c993ab768 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir biometria ao iniciar" }, - "premiumRequired": { - "message": "É necessária uma subscrição Premium" - }, - "premiumRequiredDesc": { - "message": "É necessária uma subscrição Premium para utilizar esta funcionalidade." - }, "authenticationTimeout": { "message": "Tempo limite de autenticação" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "Os URLs devem usar HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Predefinido ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar deteção de correspondência para $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Excelente trabalho ao proteger as suas credenciais em risco!" }, + "upgradeNow": { + "message": "Atualizar agora" + }, + "builtInAuthenticator": { + "message": "Autenticador incorporado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de ficheiros" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitorização de violações" + }, + "andMoreFeatures": { + "message": "E muito mais!" + }, + "planDescPremium": { + "message": "Segurança total online" + }, + "upgradeToPremium": { + "message": "Atualizar para o Premium" + }, "settingDisabledByPolicy": { "message": "Esta configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 0206f473448..4e1ac8ae832 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Solicitați date biometrice la pornire" }, - "premiumRequired": { - "message": "Premium necesar" - }, - "premiumRequiredDesc": { - "message": "Pentru a utiliza această funcție este necesar un abonament Premium." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mediu personalizat" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index b69494d472e..d59fc34f736 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Запрашивать биометрию при запуске" }, - "premiumRequired": { - "message": "Требуется Премиум" - }, - "premiumRequiredDesc": { - "message": "Для использования этой функции необходим Премиум." - }, "authenticationTimeout": { "message": "Таймаут аутентификации" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." }, + "selfHostedEnvMustUseHttps": { + "message": "URL должны использовать HTTPS." + }, "customEnvironment": { "message": "Пользовательское окружение" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "По умолчанию ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показать обнаружение совпадений $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Отличная работа по защите ваших логинов, подверженных риску!" }, + "upgradeNow": { + "message": "Изменить сейчас" + }, + "builtInAuthenticator": { + "message": "Встроенный аутентификатор" + }, + "secureFileStorage": { + "message": "Защищенное хранилище файлов" + }, + "emergencyAccess": { + "message": "Экстренный доступ" + }, + "breachMonitoring": { + "message": "Мониторинг нарушений" + }, + "andMoreFeatures": { + "message": "И многое другое!" + }, + "planDescPremium": { + "message": "Полная онлайн-защищенность" + }, + "upgradeToPremium": { + "message": "Обновить до Премиум" + }, "settingDisabledByPolicy": { "message": "Этот параметр отключен политикой вашей организации.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 60ce2436254..8c5961153eb 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "වාරික අවශ්ය" - }, - "premiumRequiredDesc": { - "message": "මෙම අංගය භාවිතා කිරීම සඳහා වාරික සාමාජිකත්වයක් අවශ්ය වේ." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "අභිරුචි පරිසරය" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 46ff6837c70..b459c86c236 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pri spustení požiadať o biometriu" }, - "premiumRequired": { - "message": "Vyžaduje sa prémiový účet" - }, - "premiumRequiredDesc": { - "message": "Na použitie tejto funkcie je potrebné prémiové členstvo." - }, "authenticationTimeout": { "message": "Časový limit overenia" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte pridať buď základnú adresu URL servera, alebo aspoň jedno vlastné prostredie." }, + "selfHostedEnvMustUseHttps": { + "message": "Adresy URL musia používať HTTPS." + }, "customEnvironment": { "message": "Vlastné prostredie" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Predvolené ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Zobraziť zisťovanie zhody $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Skvelá práca pri zabezpečení vašich ohrozených prihlasovacích údajov!" }, + "upgradeNow": { + "message": "Upgradovať teraz" + }, + "builtInAuthenticator": { + "message": "Zabudovaný autentifikátor" + }, + "secureFileStorage": { + "message": "Bezpečné ukladanie súborov" + }, + "emergencyAccess": { + "message": "Núdzový prístup" + }, + "breachMonitoring": { + "message": "Sledovanie únikov" + }, + "andMoreFeatures": { + "message": "A ešte viac!" + }, + "planDescPremium": { + "message": "Úplné online zabezpečenie" + }, + "upgradeToPremium": { + "message": "Upgradovať na Prémium" + }, "settingDisabledByPolicy": { "message": "Politika organizácie vypla toto nastavenie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 53f7a9d8f03..0a6266636b3 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ob zagonu zahtevaj biometrično preverjanje" }, - "premiumRequired": { - "message": "Potrebno je premium članstvo" - }, - "premiumRequiredDesc": { - "message": "Premium članstvo je potrebno za uporabo te funkcije." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Okolje po meri" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 169713c5047..0158ca6ba2b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Захтевај биометрију при покретању" }, - "premiumRequired": { - "message": "Потребан Премијум" - }, - "premiumRequiredDesc": { - "message": "Премијум чланство је неопходно за употребу ове опције." - }, "authenticationTimeout": { "message": "Истекло је време аутентификације" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Прилагођено окружење" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Прикажи откривање подударања $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 3b84369db47..057a7ca746c 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -32,7 +32,7 @@ "message": "Använd Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Din organisation kräver single sign-on." }, "welcomeBack": { "message": "Välkommen tillbaka" @@ -87,7 +87,7 @@ "message": "Huvudlösenordsledtråd (valfri)" }, "passwordStrengthScore": { - "message": "Lösenordsstyrka $SCORE$ (score)", + "message": "Lösenordsstyrka $SCORE$", "placeholders": { "score": { "content": "$1", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Avsluta anslutningen till denna organisation genom att ange ett huvudlösenord." + "message": "Slutför anslutningen till den här organisationen genom att ställa in ett huvudlösenord." }, "tab": { "message": "Flik" @@ -592,7 +592,7 @@ "message": "Visa" }, "viewAll": { - "message": "View all" + "message": "Visa alla" }, "viewLogin": { "message": "Visa inloggning" @@ -1035,10 +1035,10 @@ "message": "Objekt sparat" }, "savedWebsite": { - "message": "Saved website" + "message": "Sparad webbplats" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Sparade webbplatser ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Be om biometri vid start" }, - "premiumRequired": { - "message": "Premium krävs" - }, - "premiumRequiredDesc": { - "message": "Ett premium-medlemskap krävs för att använda den här funktionen." - }, "authenticationTimeout": { "message": "Timeout för autentisering" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Du måste lägga till antingen serverns bas-URL eller minst en anpassad miljö." }, + "selfHostedEnvMustUseHttps": { + "message": "Webbadresser måste använda HTTPS." + }, "customEnvironment": { "message": "Anpassad miljö" }, @@ -1695,28 +1692,28 @@ "message": "Stäng av autofyll" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Bekräfta autofyll" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Denna webbplats matchar inte dina sparade inloggningsuppgifter. Innan du fyller i dina inloggningsuppgifter, se till att det är en betrodd webbplats." }, "showInlineMenuLabel": { "message": "Visa förslag för autofyll i formulärfält" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Hur skyddar Bitwarden dina data från nätfiske?" }, "currentWebsite": { - "message": "Current website" + "message": "Aktuell webbplats" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Autofyll och lägg till denna webbplats" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Autofyll utan att lägga till" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Autofyll inte" }, "showInlineMenuIdentitiesLabel": { "message": "Visa identiteter som förslag" @@ -3280,7 +3277,7 @@ "message": "Dekrypteringsfel" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Fel vid hämtning av autofylldata" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden kunde inte dekryptera valvföremålet/valvföremålen som listas nedan." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Kan inte autofylla" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Standardmatchning är satt till 'Exakt matchning'. Den aktuella webbplatsen matchar inte exakt de sparade inloggningsuppgifterna för detta objekt." }, "okay": { - "message": "Okay" + "message": "Okej" }, "toggleSideNavigation": { "message": "Växla sidonavigering" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standard ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Visa matchningsdetektering $WEBSITE", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Bra jobbat med att säkra upp dina inloggninar i riskzonen!" }, + "upgradeNow": { + "message": "Uppgradera nu" + }, + "builtInAuthenticator": { + "message": "Inbyggd autenticator" + }, + "secureFileStorage": { + "message": "Säker fillagring" + }, + "emergencyAccess": { + "message": "Nödåtkomst" + }, + "breachMonitoring": { + "message": "Intrångsmonitorering" + }, + "andMoreFeatures": { + "message": "och mer!" + }, + "planDescPremium": { + "message": "Komplett säkerhet online" + }, + "upgradeToPremium": { + "message": "Uppgradera till Premium" + }, "settingDisabledByPolicy": { "message": "Denna inställning är inaktiverad enligt din organisations policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Postnummer" }, "cardNumberLabel": { - "message": "Card number" + "message": "Kortnummer" } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index a72a4910ef8..43944875889 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "தொடங்கும் போது பயோமெட்ரிக்ஸைக் கேட்கவும்" }, - "premiumRequired": { - "message": "பிரீமியம் தேவை" - }, - "premiumRequiredDesc": { - "message": "இந்த அம்சத்தைப் பயன்படுத்த ஒரு பிரீமியம் மெம்பர்ஷிப் தேவை." - }, "authenticationTimeout": { "message": "அங்கீகரிப்பு டைம் அவுட்" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "நீங்கள் பேஸ் சர்வர் URL-ஐ அல்லது குறைந்தது ஒரு தனிப்பயன் சூழலைச் சேர்க்க வேண்டும்." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "தனிப்பயன் சூழல்" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "பொருத்தமான கண்டறிதலைக் காட்டு $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index dd27da81316..e92192dafa0 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium Required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index d982d0f3a1a..543560810fe 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -32,7 +32,7 @@ "message": "Çoklu oturum açma kullan" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Kuruluşunuz çoklu oturum açma gerektiriyor." }, "welcomeBack": { "message": "Tekrar hoş geldiniz" @@ -1035,10 +1035,10 @@ "message": "Hesap kaydedildi" }, "savedWebsite": { - "message": "Saved website" + "message": "Kayıtlı web sitesi" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Kayıtlı web siteleri ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Açılışta biyometri doğrulaması iste" }, - "premiumRequired": { - "message": "Premium gerekli" - }, - "premiumRequiredDesc": { - "message": "Bu özelliği kullanmak için premium üyelik gereklidir." - }, "authenticationTimeout": { "message": "Kimlik doğrulama zaman aşımı" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Temel Sunucu URL’sini veya en az bir özel ortam eklemelisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL'ler HTTPS kullanmalıdır." + }, "customEnvironment": { "message": "Özel ortam" }, @@ -1695,28 +1692,28 @@ "message": "Otomatik doldurmayı kapat" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Otomatik doldurmayı onayla" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Bu site kayıtlı hesap bilgilerinizle eşleşmiyor. Hesap bilgilerinizi doldurmadan önce sitenin güvenilir olduğundan emin olun." }, "showInlineMenuLabel": { "message": "Form alanlarında otomatik doldurma önerilerini göster" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden verilerinizi kimlik avı saldırılarından nasıl koruyor?" }, "currentWebsite": { - "message": "Current website" + "message": "Geçerli web sitesi" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Otomatik doldur ve bu siteyi ekle" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Eklemeden otomatik doldur" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Otomatik doldurma" }, "showInlineMenuIdentitiesLabel": { "message": "Kimlikleri öneri olarak göster" @@ -3280,7 +3277,7 @@ "message": "Şifre çözme sorunu" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Otomatik doldurma verileri alınırken hata oluştu" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden aşağıdaki kasa öğelerini deşifre edemedi." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Otomatik doldurulamıyor" }, "cannotAutofillExactMatch": { "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." }, "okay": { - "message": "Okay" + "message": "Tamam" }, "toggleSideNavigation": { "message": "Kenar menüsünü aç/kapat" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Varsayılan ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ eşleşme tespitini göster", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Şimdi yükselt" + }, + "builtInAuthenticator": { + "message": "Dahili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvenli dosya depolama" + }, + "emergencyAccess": { + "message": "Acil durum erişimi" + }, + "breachMonitoring": { + "message": "İhlal izleme" + }, + "andMoreFeatures": { + "message": "Ve daha fazlası!" + }, + "planDescPremium": { + "message": "Eksiksiz çevrimiçi güvenlik" + }, + "upgradeToPremium": { + "message": "Premium'a yükselt" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index aa118c0b93e..2c6fa4eb15b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -32,7 +32,7 @@ "message": "Використати єдиний вхід" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша організація вимагає єдиний вхід (SSO)." }, "welcomeBack": { "message": "З поверненням" @@ -592,7 +592,7 @@ "message": "Переглянути" }, "viewAll": { - "message": "View all" + "message": "Переглянути все" }, "viewLogin": { "message": "Переглянути запис" @@ -1035,10 +1035,10 @@ "message": "Запис збережено" }, "savedWebsite": { - "message": "Saved website" + "message": "Збережений вебсайт" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Збережені вебсайти ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1440,22 +1440,22 @@ "message": "Застаріле шифрування більше не підтримується. Зверніться до служби підтримки, щоб відновити обліковий запис." }, "premiumMembership": { - "message": "Преміум статус" + "message": "Передплата Premium" }, "premiumManage": { "message": "Керувати передплатою" }, "premiumManageAlert": { - "message": "Ви можете керувати своїм статусом у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" + "message": "Ви можете керувати передплатою у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" }, "premiumRefresh": { "message": "Оновити стан передплати" }, "premiumNotCurrentMember": { - "message": "Зараз у вас немає передплати преміум." + "message": "Зараз у вас немає передплати Premium." }, "premiumSignUpAndGet": { - "message": "Передплатіть преміум і отримайте:" + "message": "Передплатіть Premium і отримайте:" }, "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." @@ -1476,25 +1476,25 @@ "message": "Пріоритетну технічну підтримку." }, "ppremiumSignUpFuture": { - "message": "Усі майбутні преміумфункції. Їх буде більше!" + "message": "Усі майбутні функції Premium. Їх буде більше!" }, "premiumPurchase": { - "message": "Придбати преміум" + "message": "Придбати Premium" }, "premiumPurchaseAlertV2": { - "message": "Ви можете придбати Преміум у налаштуваннях облікового запису вебпрограмі Bitwarden." + "message": "Ви можете придбати Premium у налаштуваннях облікового запису вебпрограми Bitwarden." }, "premiumCurrentMember": { - "message": "Ви користуєтеся передплатою преміум!" + "message": "Ви користуєтеся передплатою Premium!" }, "premiumCurrentMemberThanks": { "message": "Дякуємо за підтримку Bitwarden." }, "premiumFeatures": { - "message": "Передплатіть преміум та отримайте:" + "message": "Передплатіть Premium та отримайте:" }, "premiumPrice": { - "message": "Всього лише $PRICE$ / за рік!", + "message": "Лише $PRICE$ / рік!", "placeholders": { "price": { "content": "$1", @@ -1503,7 +1503,7 @@ } }, "premiumPriceV2": { - "message": "Усе лише за $PRICE$ на рік!", + "message": "Лише за $PRICE$ на рік за все!", "placeholders": { "price": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Запитувати біометрію під час запуску" }, - "premiumRequired": { - "message": "Необхідна передплата преміум" - }, - "premiumRequiredDesc": { - "message": "Для використання цієї функції необхідна передплата преміум." - }, "authenticationTimeout": { "message": "Час очікування автентифікації" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-адреси повинні бути HTTPS." + }, "customEnvironment": { "message": "Власне середовище" }, @@ -1695,28 +1692,28 @@ "message": "Вимкніть автозаповнення" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Підтвердити автозаповнення" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Адреса цього вебсайту відрізняється від збережених даних вашого запису. Перш ніж заповнити облікові дані, переконайтеся, що це надійний сайт." }, "showInlineMenuLabel": { "message": "Пропозиції автозаповнення на полях форм" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Як Bitwarden захищає ваші дані від шахрайства?" }, "currentWebsite": { - "message": "Current website" + "message": "Поточний вебсайт" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Автоматично заповнити й додати цей сайт" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Автоматично заповнити без додавання" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Не заповнювати автоматично" }, "showInlineMenuIdentitiesLabel": { "message": "Показувати посвідчення як пропозиції" @@ -3280,7 +3277,7 @@ "message": "Помилка розшифрування" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Помилка отримання даних автозаповнення" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden не зміг розшифрувати вказані нижче елементи сховища." @@ -3512,7 +3509,7 @@ "message": "Помилка Key Connector: переконайтеся, що Key Connector доступний та працює правильно." }, "premiumSubcriptionRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "organizationIsDisabled": { "message": "Організацію вимкнено." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Неможливо автоматично заповнити" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Типово налаштовано \"Точну відповідність\". Адреса поточного вебсайту відрізняється від збережених даних для цього запису." }, "okay": { - "message": "Okay" + "message": "Гаразд" }, "toggleSideNavigation": { "message": "Перемкнути бічну навігацію" @@ -4891,7 +4888,7 @@ "message": "Ви дійсно хочете остаточно видалити це вкладення?" }, "premium": { - "message": "Преміум" + "message": "Premium" }, "freeOrgsCannotUseAttachments": { "message": "Організації без передплати не можуть використовувати вкладення" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показати виявлення збігів $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Ви чудово впоралися із захистом своїх ризикованих записів!" }, + "upgradeNow": { + "message": "Покращити" + }, + "builtInAuthenticator": { + "message": "Вбудований автентифікатор" + }, + "secureFileStorage": { + "message": "Захищене сховище файлів" + }, + "emergencyAccess": { + "message": "Екстрений доступ" + }, + "breachMonitoring": { + "message": "Моніторинг витоків даних" + }, + "andMoreFeatures": { + "message": "Інші можливості!" + }, + "planDescPremium": { + "message": "Повна онлайн-безпека" + }, + "upgradeToPremium": { + "message": "Покращити до Premium" + }, "settingDisabledByPolicy": { "message": "Цей параметр вимкнено політикою вашої організації.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Поштовий індекс" }, "cardNumberLabel": { - "message": "Card number" + "message": "Номер картки" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index fff32a542cc..8029f5b2c46 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -32,7 +32,7 @@ "message": "Dùng đăng nhập một lần" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tổ chức của bạn yêu cầu đăng nhập một lần." }, "welcomeBack": { "message": "Chào mừng bạn trở lại" @@ -592,7 +592,7 @@ "message": "Xem" }, "viewAll": { - "message": "View all" + "message": "Xem tất cả" }, "viewLogin": { "message": "Xem đăng nhập" @@ -1035,10 +1035,10 @@ "message": "Đã lưu mục" }, "savedWebsite": { - "message": "Saved website" + "message": "Đã lưu trang web" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Đã lưu trang web ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Yêu cầu sinh trắc học khi khởi chạy" }, - "premiumRequired": { - "message": "Cần có tài khoản Cao cấp" - }, - "premiumRequiredDesc": { - "message": "Cần là thành viên Cao cấp để sử dụng tính năng này." - }, "authenticationTimeout": { "message": "Thời gian chờ xác thực" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Bạn phải thêm URL máy chủ cơ sở hoặc ít nhất một môi trường tùy chỉnh." }, + "selfHostedEnvMustUseHttps": { + "message": "URL phải sử dụng HTTPS." + }, "customEnvironment": { "message": "Môi trường tùy chỉnh" }, @@ -1695,28 +1692,28 @@ "message": "Tắt tự động điền" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Xác nhận tự động điền" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Trang web này không khớp với đăng nhập đã lưu của bạn. Trước khi bạn điền thông tin đăng nhập, hãy đảm bảo đây là trang web đáng tin cậy." }, "showInlineMenuLabel": { "message": "Hiển thị các gợi ý tự động điền trên các trường biểu mẫu" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden bảo vệ dữ liệu của bạn khỏi lừa đảo như thế nào?" }, "currentWebsite": { - "message": "Current website" + "message": "Trang web hiện tại" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Tự động điền và thêm trang web này" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Tự động điền mà không thêm" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Không tự động điền" }, "showInlineMenuIdentitiesLabel": { "message": "Hiển thị danh tính dưới dạng gợi ý" @@ -3280,7 +3277,7 @@ "message": "Lỗi giải mã" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Lỗi khi lấy dữ liệu tự động điền" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden không thể giải mã các mục trong kho lưu trữ được liệt kê bên dưới." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Không thể tự động điền" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Phép so khớp mặc định được đặt thành \"Khớp chính xác\". Trang web hiện tại không khớp chính xác với đăng nhập đã lưu cho mục này." }, "okay": { - "message": "Okay" + "message": "Đồng ý" }, "toggleSideNavigation": { "message": "Ẩn/hiện thanh điều hướng bên" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Hiện phát hiện trùng khớp $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Thật tuyệt khi bảo vệ các đăng nhập có nguy cơ của bạn!" }, + "upgradeNow": { + "message": "Nâng cấp ngay" + }, + "builtInAuthenticator": { + "message": "Trình xác thực tích hợp" + }, + "secureFileStorage": { + "message": "Lưu trữ tệp an toàn" + }, + "emergencyAccess": { + "message": "Truy cập khẩn cấp" + }, + "breachMonitoring": { + "message": "Giám sát vi phạm" + }, + "andMoreFeatures": { + "message": "Và nhiều hơn nữa!" + }, + "planDescPremium": { + "message": "Bảo mật trực tuyến toàn diện" + }, + "upgradeToPremium": { + "message": "Nâng cấp lên gói Cao cấp" + }, "settingDisabledByPolicy": { "message": "Cài đặt này bị vô hiệu hóa bởi chính sách tổ chức của bạn.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Mã ZIP / Bưu điện" }, "cardNumberLabel": { - "message": "Card number" + "message": "Số thẻ" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 16f41e4e987..e59a74e358d 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -32,7 +32,7 @@ "message": "使用单点登录" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的组织要求单点登录。" }, "welcomeBack": { "message": "欢迎回来" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "启动时提示生物识别" }, - "premiumRequired": { - "message": "需要高级会员" - }, - "premiumRequiredDesc": { - "message": "使用此功能需要高级会员资格。" - }, "authenticationTimeout": { "message": "身份验证超时" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必须使用 HTTPS。" + }, "customEnvironment": { "message": "自定义环境" }, @@ -4968,7 +4965,17 @@ "message": "删除网站" }, "defaultLabel": { - "message": "默认 ($VALUE$)", + "message": "默认($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, + "defaultLabelWithValue": { + "message": "默认($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5592,7 +5599,7 @@ "message": "欢迎使用 Bitwarden" }, "securityPrioritized": { - "message": "安全优先" + "message": "以安全为首要" }, "securityPrioritizedBody": { "message": "将登录、支付卡和身份保存到您的安全密码库。Bitwarden 使用零知识、端到端的加密来保护您的重要信息。" @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "很好地保护了存在风险的登录!" }, + "upgradeNow": { + "message": "立即升级" + }, + "builtInAuthenticator": { + "message": "内置身份验证器" + }, + "secureFileStorage": { + "message": "安全文件存储" + }, + "emergencyAccess": { + "message": "紧急访问" + }, + "breachMonitoring": { + "message": "数据泄露监测" + }, + "andMoreFeatures": { + "message": "以及更多!" + }, + "planDescPremium": { + "message": "全面的在线安全防护" + }, + "upgradeToPremium": { + "message": "升级为高级版" + }, "settingDisabledByPolicy": { "message": "此设置被您组织的策略禁用了。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index d3c0319e488..63f3ea59f60 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -32,7 +32,7 @@ "message": "使用單一登入" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的組織需要單一登入。" }, "welcomeBack": { "message": "歡迎回來" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "啟動時要求生物特徵辨識" }, - "premiumRequired": { - "message": "需要進階會員資格" - }, - "premiumRequiredDesc": { - "message": "進階會員才可使用此功能。" - }, "authenticationTimeout": { "message": "驗證逾時" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "您必須新增伺服器網域 URL 或至少一個自訂環境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必須使用 HTTPS。" + }, "customEnvironment": { "message": "自訂環境" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "你已成功保護有風險的登入項目,做得好!" }, + "upgradeNow": { + "message": "立即升級" + }, + "builtInAuthenticator": { + "message": "內建驗證器" + }, + "secureFileStorage": { + "message": "安全檔案儲存" + }, + "emergencyAccess": { + "message": "緊急存取" + }, + "breachMonitoring": { + "message": "外洩監控" + }, + "andMoreFeatures": { + "message": "以及其他功能功能!" + }, + "planDescPremium": { + "message": "完整的線上安全" + }, + "upgradeToPremium": { + "message": "升級到 Premium" + }, "settingDisabledByPolicy": { "message": "此設定已被你的組織原則停用。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts index 9e9a1ecf570..d7d3c02ab14 100644 --- a/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts +++ b/apps/browser/src/auth/popup/account-switching/account-switcher.component.ts @@ -122,10 +122,8 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy { async lock(userId: string) { this.loading = true; - await this.vaultTimeoutService.lock(userId); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["lock"]); + await this.lockService.lock(userId as UserId); + await this.router.navigate(["lock"]); } async lockAll() { diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d22ce9c9366..90770bb8d9b 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -25,7 +25,7 @@
( - {{ + {{ status.text }} ) diff --git a/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts b/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts index 20a52a90d8b..91adecd4a03 100644 --- a/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts +++ b/apps/browser/src/auth/popup/accounts/foreground-lock.service.ts @@ -6,10 +6,13 @@ import { MessageListener, MessageSender, } from "@bitwarden/common/platform/messaging"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { newGuid } from "@bitwarden/guid"; +import { UserId } from "@bitwarden/user-core"; const LOCK_ALL_FINISHED = new CommandDefinition<{ requestId: string }>("lockAllFinished"); const LOCK_ALL = new CommandDefinition<{ requestId: string }>("lockAll"); +const LOCK_USER_FINISHED = new CommandDefinition<{ requestId: string }>("lockUserFinished"); +const LOCK_USER = new CommandDefinition<{ requestId: string; userId: UserId }>("lockUser"); export class ForegroundLockService implements LockService { constructor( @@ -18,7 +21,7 @@ export class ForegroundLockService implements LockService { ) {} async lockAll(): Promise { - const requestId = Utils.newGuid(); + const requestId = newGuid(); const finishMessage = firstValueFrom( this.messageListener .messages$(LOCK_ALL_FINISHED) @@ -29,4 +32,19 @@ export class ForegroundLockService implements LockService { await finishMessage; } + + async lock(userId: UserId): Promise { + const requestId = newGuid(); + const finishMessage = firstValueFrom( + this.messageListener + .messages$(LOCK_USER_FINISHED) + .pipe(filter((m) => m.requestId === requestId)), + ); + + this.messageSender.send(LOCK_USER, { requestId, userId }); + + await finishMessage; + } + + async runPlatformOnLockActions(): Promise {} } diff --git a/apps/browser/src/auth/popup/components/set-pin.component.html b/apps/browser/src/auth/popup/components/set-pin.component.html index d525f9378f1..c88274b2bf4 100644 --- a/apps/browser/src/auth/popup/components/set-pin.component.html +++ b/apps/browser/src/auth/popup/components/set-pin.component.html @@ -1,6 +1,6 @@
-
+
{{ "setYourPinTitle" | i18n }}
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts index aa3639e9e93..28639cd1ed5 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.spec.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.spec.ts @@ -6,6 +6,7 @@ import { mock } from "jest-mock-extended"; import { firstValueFrom, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; +import { LockService } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -16,7 +17,6 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction"; import { VaultTimeoutSettingsService, - VaultTimeoutService, VaultTimeoutStringType, VaultTimeoutAction, } from "@bitwarden/common/key-management/vault-timeout"; @@ -63,6 +63,7 @@ describe("AccountSecurityComponent", () => { const validationService = mock(); const dialogService = mock(); const platformUtilsService = mock(); + const lockService = mock(); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -83,7 +84,6 @@ describe("AccountSecurityComponent", () => { { provide: PopupRouterCacheService, useValue: mock() }, { provide: ToastService, useValue: mock() }, { provide: UserVerificationService, useValue: mock() }, - { provide: VaultTimeoutService, useValue: mock() }, { provide: VaultTimeoutSettingsService, useValue: vaultTimeoutSettingsService }, { provide: StateProvider, useValue: mock() }, { provide: CipherService, useValue: mock() }, @@ -92,6 +92,7 @@ describe("AccountSecurityComponent", () => { { provide: OrganizationService, useValue: mock() }, { provide: CollectionService, useValue: mock() }, { provide: ValidationService, useValue: validationService }, + { provide: LockService, useValue: lockService }, ], }) .overrideComponent(AccountSecurityComponent, { diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 65a0d33f93e..4a5388ef266 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -25,6 +25,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component"; import { FingerprintDialogComponent, VaultTimeoutInputComponent } from "@bitwarden/auth/angular"; +import { LockService } from "@bitwarden/auth/common"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { getFirstPolicy } from "@bitwarden/common/admin-console/services/policy/default-policy.service"; @@ -36,7 +37,6 @@ import { VaultTimeout, VaultTimeoutAction, VaultTimeoutOption, - VaultTimeoutService, VaultTimeoutSettingsService, VaultTimeoutStringType, } from "@bitwarden/common/key-management/vault-timeout"; @@ -143,7 +143,7 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, - private vaultTimeoutService: VaultTimeoutService, + private lockService: LockService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, public messagingService: MessagingService, private environmentService: EnvironmentService, @@ -695,7 +695,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { } async lock() { - await this.vaultTimeoutService.lock(); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.lockService.lock(activeUserId); } async logOut() { diff --git a/apps/browser/src/auth/services/extension-lock.service.ts b/apps/browser/src/auth/services/extension-lock.service.ts new file mode 100644 index 00000000000..7e01e8155e7 --- /dev/null +++ b/apps/browser/src/auth/services/extension-lock.service.ts @@ -0,0 +1,58 @@ +import { DefaultLockService, LogoutService } from "@bitwarden/auth/common"; +import MainBackground from "@bitwarden/browser/background/main.background"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { VaultTimeoutSettingsService } from "@bitwarden/common/key-management/vault-timeout"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; +import { BiometricsService, KeyService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; +import { StateEventRunnerService } from "@bitwarden/state"; + +export class ExtensionLockService extends DefaultLockService { + constructor( + accountService: AccountService, + biometricService: BiometricsService, + vaultTimeoutSettingsService: VaultTimeoutSettingsService, + logoutService: LogoutService, + messagingService: MessagingService, + searchService: SearchService, + folderService: FolderService, + masterPasswordService: InternalMasterPasswordServiceAbstraction, + stateEventRunnerService: StateEventRunnerService, + cipherService: CipherService, + authService: AuthService, + systemService: SystemService, + processReloadService: ProcessReloadServiceAbstraction, + logService: LogService, + keyService: KeyService, + private readonly main: MainBackground, + ) { + super( + accountService, + biometricService, + vaultTimeoutSettingsService, + logoutService, + messagingService, + searchService, + folderService, + masterPasswordService, + stateEventRunnerService, + cipherService, + authService, + systemService, + processReloadService, + logService, + keyService, + ); + } + + async runPlatformOnLockActions(): Promise { + await this.main.refreshMenu(true); + } +} diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index 912d9657124..e50a317e8a7 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -147,7 +147,7 @@ type NotificationBackgroundExtensionMessageHandlers = { bgGetEnableChangedPasswordPrompt: () => Promise; bgGetEnableAddedLoginPrompt: () => Promise; bgGetExcludedDomains: () => Promise; - bgGetActiveUserServerConfig: () => Promise; + bgGetActiveUserServerConfig: () => Promise; getWebVaultUrlForNotification: () => Promise; }; diff --git a/apps/browser/src/autofill/background/abstractions/overlay.background.ts b/apps/browser/src/autofill/background/abstractions/overlay.background.ts index 6067d563db2..18cf1d20446 100644 --- a/apps/browser/src/autofill/background/abstractions/overlay.background.ts +++ b/apps/browser/src/autofill/background/abstractions/overlay.background.ts @@ -1,19 +1,22 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; +import { CipherIconDetails } from "@bitwarden/common/vault/icon/build-cipher-icon"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { InlineMenuFillType } from "../../enums/autofill-overlay.enum"; +import AutofillField from "../../models/autofill-field"; import AutofillPageDetails from "../../models/autofill-page-details"; import { PageDetail } from "../../services/abstractions/autofill.service"; import { LockedVaultPendingNotificationsData } from "./notification.background"; -export type PageDetailsForTab = Record< - chrome.runtime.MessageSender["tab"]["id"], - Map ->; +export type TabId = NonNullable; + +export type FrameId = NonNullable; + +type PageDetailsByFrame = Map; + +export type PageDetailsForTab = Record; export type SubFrameOffsetData = { top: number; @@ -21,19 +24,14 @@ export type SubFrameOffsetData = { url?: string; frameId?: number; parentFrameIds?: number[]; + isCrossOriginSubframe?: boolean; + isMainFrame?: boolean; + hasParentFrame?: boolean; } | null; -export type SubFrameOffsetsForTab = Record< - chrome.runtime.MessageSender["tab"]["id"], - Map ->; +type SubFrameOffsetsByFrame = Map; -export type WebsiteIconData = { - imageEnabled: boolean; - image: string; - fallbackImage: string; - icon: string; -}; +export type SubFrameOffsetsForTab = Record; export type UpdateOverlayCiphersParams = { updateAllCipherTypes: boolean; @@ -146,7 +144,7 @@ export type OverlayBackgroundExtensionMessage = { isFieldCurrentlyFilling?: boolean; subFrameData?: SubFrameOffsetData; focusedFieldData?: FocusedFieldData; - allFieldsRect?: any; + allFieldsRect?: AutofillField[]; isOpeningFullInlineMenu?: boolean; styles?: Partial; data?: LockedVaultPendingNotificationsData; @@ -155,13 +153,30 @@ export type OverlayBackgroundExtensionMessage = { ToggleInlineMenuHiddenMessage & UpdateInlineMenuVisibilityMessage; +export type OverlayPortCommand = + | "fillCipher" + | "addNewVaultItem" + | "viewCipher" + | "redirectFocus" + | "updateHeight" + | "buttonClicked" + | "blurred" + | "updateColorScheme" + | "unlockVault" + | "refreshGeneratedPassword" + | "fillGeneratedPassword"; + export type OverlayPortMessage = { - [key: string]: any; - command: string; - direction?: string; + command: OverlayPortCommand; + direction?: "up" | "down" | "left" | "right"; inlineMenuCipherId?: string; addNewCipherType?: CipherType; usePasskey?: boolean; + height?: number; + backgroundColorScheme?: "light" | "dark"; + viewsCipherData?: InlineMenuCipherData; + loginUrl?: string; + fillGeneratedPassword?: boolean; }; export type InlineMenuCipherData = { @@ -170,7 +185,7 @@ export type InlineMenuCipherData = { type: CipherType; reprompt: CipherRepromptType; favorite: boolean; - icon: WebsiteIconData; + icon: CipherIconDetails; accountCreationFieldType?: string; login?: { totp?: string; @@ -201,9 +216,14 @@ export type BuildCipherDataParams = { export type BackgroundMessageParam = { message: OverlayBackgroundExtensionMessage; }; + export type BackgroundSenderParam = { - sender: chrome.runtime.MessageSender; + sender: chrome.runtime.MessageSender & { + tab: NonNullable; + frameId: FrameId; + }; }; + export type BackgroundOnMessageHandlerParams = BackgroundMessageParam & BackgroundSenderParam; export type OverlayBackgroundExtensionMessageHandlers = { @@ -253,9 +273,13 @@ export type OverlayBackgroundExtensionMessageHandlers = { export type PortMessageParam = { message: OverlayPortMessage; }; + export type PortConnectionParam = { - port: chrome.runtime.Port; + port: chrome.runtime.Port & { + sender: NonNullable; + }; }; + export type PortOnMessageHandlerParams = PortMessageParam & PortConnectionParam; export type InlineMenuButtonPortMessageHandlers = { diff --git a/apps/browser/src/autofill/background/context-menus.background.ts b/apps/browser/src/autofill/background/context-menus.background.ts index 0db2fd59af3..8c99c0b065e 100644 --- a/apps/browser/src/autofill/background/context-menus.background.ts +++ b/apps/browser/src/autofill/background/context-menus.background.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { BrowserApi } from "../../platform/browser/browser-api"; import { ContextMenuClickedHandler } from "../browser/context-menu-clicked-handler"; @@ -17,9 +15,11 @@ export default class ContextMenusBackground { return; } - this.contextMenus.onClicked.addListener((info, tab) => - this.contextMenuClickedHandler.run(info, tab), - ); + this.contextMenus.onClicked.addListener((info, tab) => { + if (tab) { + return this.contextMenuClickedHandler.run(info, tab); + } + }); BrowserApi.messageListener( "contextmenus.background", @@ -28,18 +28,16 @@ export default class ContextMenusBackground { sender: chrome.runtime.MessageSender, ) => { if (msg.command === "unlockCompleted" && msg.data.target === "contextmenus.background") { - // 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.contextMenuClickedHandler - .cipherAction( - msg.data.commandToRetry.message.contextMenuOnClickData, - msg.data.commandToRetry.sender.tab, - ) - .then(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar"); + const onClickData = msg.data.commandToRetry.message.contextMenuOnClickData; + const senderTab = msg.data.commandToRetry.sender.tab; + + if (onClickData && senderTab) { + void this.contextMenuClickedHandler.cipherAction(onClickData, senderTab).then(() => { + if (sender.tab) { + void BrowserApi.tabSendMessageData(sender.tab, "closeNotificationBar"); + } }); + } } }, ); diff --git a/apps/browser/src/autofill/background/tabs.background.spec.ts b/apps/browser/src/autofill/background/tabs.background.spec.ts index 635ab8504a1..7bfa3b83c16 100644 --- a/apps/browser/src/autofill/background/tabs.background.spec.ts +++ b/apps/browser/src/autofill/background/tabs.background.spec.ts @@ -39,9 +39,7 @@ describe("TabsBackground", () => { "handleWindowOnFocusChanged", ); - // 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 - tabsBackground.init(); + void tabsBackground.init(); expect(chrome.windows.onFocusChanged.addListener).toHaveBeenCalledWith( handleWindowOnFocusChangedSpy, diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index c33cb6a4371..6f0979d4fd5 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -191,9 +191,11 @@ export class ContextMenuClickedHandler { }); } else { this.copyToClipboard({ text: cipher.login.password, tab: tab }); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.eventCollectionService.collect(EventType.Cipher_ClientCopiedPassword, cipher.id); + + void this.eventCollectionService.collect( + EventType.Cipher_ClientCopiedPassword, + cipher.id, + ); } break; diff --git a/apps/browser/src/autofill/browser/main-context-menu-handler.ts b/apps/browser/src/autofill/browser/main-context-menu-handler.ts index 00ff55f5517..5a47975684c 100644 --- a/apps/browser/src/autofill/browser/main-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/main-context-menu-handler.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -179,9 +177,11 @@ export class MainContextMenuHandler { try { const account = await firstValueFrom(this.accountService.activeAccount$); - const hasPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), - ); + const hasPremium = + !!account?.id && + (await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + )); const isCardRestricted = ( await firstValueFrom(this.restrictedItemTypesService.restricted$) @@ -198,14 +198,16 @@ export class MainContextMenuHandler { if (requiresPremiumAccess && !hasPremium) { continue; } - if (menuItem.id.startsWith(AUTOFILL_CARD_ID) && isCardRestricted) { + if (menuItem.id?.startsWith(AUTOFILL_CARD_ID) && isCardRestricted) { continue; } await MainContextMenuHandler.create({ ...otherOptions, contexts: ["all"] }); } } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } finally { this.initRunning = false; } @@ -318,9 +320,11 @@ export class MainContextMenuHandler { } const account = await firstValueFrom(this.accountService.activeAccount$); - const canAccessPremium = await firstValueFrom( - this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), - ); + const canAccessPremium = + !!account?.id && + (await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + )); if (canAccessPremium && (!cipher || !Utils.isNullOrEmpty(cipher.login?.totp))) { await createChildItem(COPY_VERIFICATION_CODE_ID); } @@ -333,7 +337,9 @@ export class MainContextMenuHandler { await createChildItem(AUTOFILL_IDENTITY_ID); } } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } } @@ -351,7 +357,11 @@ export class MainContextMenuHandler { this.loadOptions( this.i18nService.t(authed ? "unlockVaultMenu" : "loginToVaultMenu"), NOOP_COMMAND_SUFFIX, - ).catch((error) => this.logService.warning(error.message)); + ).catch((error) => { + if (error instanceof Error) { + return this.logService.warning(error.message); + } + }); } } @@ -363,7 +373,9 @@ export class MainContextMenuHandler { } } } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } } @@ -373,7 +385,9 @@ export class MainContextMenuHandler { await MainContextMenuHandler.create(menuItem); } } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } } @@ -383,7 +397,9 @@ export class MainContextMenuHandler { await MainContextMenuHandler.create(menuItem); } } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } } @@ -395,7 +411,9 @@ export class MainContextMenuHandler { await this.loadOptions(this.i18nService.t("addLoginMenu"), CREATE_LOGIN_ID); } catch (error) { - this.logService.warning(error.message); + if (error instanceof Error) { + this.logService.warning(error.message); + } } } } diff --git a/apps/browser/src/autofill/content/auto-submit-login.ts b/apps/browser/src/autofill/content/auto-submit-login.ts index ca5c8ebee80..511d35d7a49 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import AutofillPageDetails from "../models/autofill-page-details"; @@ -123,9 +121,9 @@ import { * @param fillScript - The autofill script to use */ function triggerAutoSubmitOnForm(fillScript: AutofillScript) { - const formOpid = fillScript.autosubmit[0]; + const formOpid = fillScript.autosubmit?.[0]; - if (formOpid === null) { + if (!formOpid) { triggerAutoSubmitOnFormlessFields(fillScript); return; } @@ -159,8 +157,11 @@ import { fillScript.script[fillScript.script.length - 1][1], ); - const lastFieldIsPasswordInput = - elementIsInputElement(currentElement) && currentElement.type === "password"; + const lastFieldIsPasswordInput = !!( + currentElement && + elementIsInputElement(currentElement) && + currentElement.type === "password" + ); while (currentElement && currentElement.tagName !== "HTML") { if (submitElementFoundAndClicked(currentElement, lastFieldIsPasswordInput)) { diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index b43bed7f96b..73fc1e79ec5 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -68,7 +68,7 @@ const actionButtonStyles = ({ overflow: hidden; text-align: center; text-overflow: ellipsis; - font-weight: 700; + font-weight: 500; ${disabled || isLoading ? ` diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts index 590311682bf..f8b5d2b85bf 100644 --- a/apps/browser/src/autofill/content/components/cipher/types.ts +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -1,3 +1,5 @@ +import { CipherIconDetails } from "@bitwarden/common/vault/icon/build-cipher-icon"; + export const CipherTypes = { Login: 1, SecureNote: 2, @@ -22,20 +24,13 @@ export const OrganizationCategories = { family: "family", } as const; -export type WebsiteIconData = { - imageEnabled: boolean; - image: string; - fallbackImage: string; - icon: string; -}; - type BaseCipherData = { id: string; name: string; type: CipherTypeValue; reprompt: CipherRepromptType; favorite: boolean; - icon: WebsiteIconData; + icon: CipherIconDetails; }; export type CipherData = BaseCipherData & { diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts index 55130781808..c1d6228459a 100644 --- a/apps/browser/src/autofill/content/components/constants/styles.ts +++ b/apps/browser/src/autofill/content/components/constants/styles.ts @@ -144,17 +144,17 @@ export const border = { export const typography = { body1: ` line-height: 24px; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 16px; `, body2: ` line-height: 20px; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 14px; `, helperMedium: ` line-height: 16px; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 12px; `, }; diff --git a/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts b/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts index 42d4907711d..9c55c1e7e2b 100644 --- a/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts +++ b/apps/browser/src/autofill/content/components/notification/at-risk-password/message.ts @@ -29,7 +29,7 @@ const baseTextStyles = css` text-align: left; text-overflow: ellipsis; line-height: 24px; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 16px; `; diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 7f15d882297..36ea9c1f9d6 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -84,7 +84,7 @@ const baseTextStyles = css` text-align: left; text-overflow: ellipsis; line-height: 24px; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 16px; `; @@ -115,7 +115,7 @@ const notificationConfirmationButtonTextStyles = (theme: Theme) => css` ${baseTextStyles} color: ${themes[theme].primary[600]}; - font-weight: 700; + font-weight: 500; cursor: pointer; `; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts index 47fe8cd2828..2e51d82dd07 100644 --- a/apps/browser/src/autofill/content/components/notification/header-message.ts +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -19,7 +19,7 @@ const notificationHeaderMessageStyles = (theme: Theme) => css` line-height: 28px; white-space: nowrap; color: ${themes[theme].text.main}; - font-family: Roboto, sans-serif; + font-family: Inter, sans-serif; font-size: 18px; - font-weight: 600; + font-weight: 500; `; diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts index ceb72905357..58216b6c1b2 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-items.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts @@ -94,7 +94,7 @@ const optionsLabelStyles = ({ theme }: { theme: Theme }) => css` user-select: none; padding: 0.375rem ${spacing["3"]}; color: ${themes[theme].text.muted}; - font-weight: 600; + font-weight: 500; `; export const optionsMenuItemMaxWidth = 260; diff --git a/apps/browser/src/autofill/content/components/rows/action-row.ts b/apps/browser/src/autofill/content/components/rows/action-row.ts index 0380f91012a..8f13b166156 100644 --- a/apps/browser/src/autofill/content/components/rows/action-row.ts +++ b/apps/browser/src/autofill/content/components/rows/action-row.ts @@ -34,7 +34,7 @@ const actionRowStyles = (theme: Theme) => css` min-height: 40px; text-align: left; color: ${themes[theme].primary["600"]}; - font-weight: 700; + font-weight: 500; > span { display: block; diff --git a/apps/browser/src/autofill/content/context-menu-handler.ts b/apps/browser/src/autofill/content/context-menu-handler.ts index 82cf95afc81..d3926d57c9a 100644 --- a/apps/browser/src/autofill/content/context-menu-handler.ts +++ b/apps/browser/src/autofill/content/context-menu-handler.ts @@ -1,43 +1,43 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore const inputTags = ["input", "textarea", "select"]; const labelTags = ["label", "span"]; -const attributes = ["id", "name", "label-aria", "placeholder"]; +const attributeKeys = ["id", "name", "label-aria", "placeholder"]; const invalidElement = chrome.i18n.getMessage("copyCustomFieldNameInvalidElement"); const noUniqueIdentifier = chrome.i18n.getMessage("copyCustomFieldNameNotUnique"); -let clickedEl: HTMLElement = null; +let clickedElement: HTMLElement | null = null; // Find the best attribute to be used as the Name for an element in a custom field. function getClickedElementIdentifier() { - if (clickedEl == null) { + if (clickedElement == null) { return invalidElement; } - const clickedTag = clickedEl.nodeName.toLowerCase(); - let inputEl = null; + const clickedTag = clickedElement.nodeName.toLowerCase(); + let inputElement = null; // Try to identify the input element (which may not be the clicked element) if (labelTags.includes(clickedTag)) { - let inputId = null; + let inputId; if (clickedTag === "label") { - inputId = clickedEl.getAttribute("for"); + inputId = clickedElement.getAttribute("for"); } else { - inputId = clickedEl.closest("label")?.getAttribute("for"); + inputId = clickedElement.closest("label")?.getAttribute("for"); } - inputEl = document.getElementById(inputId); + if (inputId) { + inputElement = document.getElementById(inputId); + } } else { - inputEl = clickedEl; + inputElement = clickedElement; } - if (inputEl == null || !inputTags.includes(inputEl.nodeName.toLowerCase())) { + if (inputElement == null || !inputTags.includes(inputElement.nodeName.toLowerCase())) { return invalidElement; } - for (const attr of attributes) { - const attributeValue = inputEl.getAttribute(attr); - const selector = "[" + attr + '="' + attributeValue + '"]'; + for (const attributeKey of attributeKeys) { + const attributeValue = inputElement.getAttribute(attributeKey); + const selector = "[" + attributeKey + '="' + attributeValue + '"]'; if (!isNullOrEmpty(attributeValue) && document.querySelectorAll(selector)?.length === 1) { return attributeValue; } @@ -45,14 +45,14 @@ function getClickedElementIdentifier() { return noUniqueIdentifier; } -function isNullOrEmpty(s: string) { +function isNullOrEmpty(s: string | null) { return s == null || s === ""; } // We only have access to the element that's been clicked when the context menu is first opened. // Remember it for use later. document.addEventListener("contextmenu", (event) => { - clickedEl = event.target as HTMLElement; + clickedElement = event.target as HTMLElement; }); // Runs when the 'Copy Custom Field Name' context menu item is actually clicked. @@ -62,9 +62,8 @@ chrome.runtime.onMessage.addListener((event, _sender, sendResponse) => { if (sendResponse) { sendResponse(identifier); } - // 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 - chrome.runtime.sendMessage({ + + void chrome.runtime.sendMessage({ command: "getClickedElementResponse", sender: "contextMenuHandler", identifier: identifier, diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index 5b9ea5e5b27..1cd614a9516 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -267,9 +267,7 @@ import { Messenger } from "./messaging/messenger"; clearWaitForFocus(); void messenger.destroy(); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { + } catch { /** empty */ } } diff --git a/apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts b/apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts index 5283c60882d..1aa8c27c0ae 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/messenger.spec.ts @@ -31,9 +31,8 @@ describe("Messenger", () => { it("should deliver message to B when sending request from A", () => { const request = createRequest(); - // 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 - messengerA.request(request); + + void messengerA.request(request); const received = handlerB.receive(); @@ -66,14 +65,13 @@ describe("Messenger", () => { it("should deliver abort signal to B when requesting abort", () => { const abortController = new AbortController(); - // 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 - messengerA.request(createRequest(), abortController.signal); + + void messengerA.request(createRequest(), abortController.signal); abortController.abort(); const received = handlerB.receive(); - expect(received[0].abortController.signal.aborted).toBe(true); + expect(received[0].abortController?.signal.aborted).toBe(true); }); describe("destroy", () => { @@ -103,29 +101,25 @@ describe("Messenger", () => { it("should dispatch the destroy event on messenger destruction", async () => { const request = createRequest(); - // 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 - messengerA.request(request); + + void messengerA.request(request); const dispatchEventSpy = jest.spyOn((messengerA as any).onDestroy, "dispatchEvent"); - // 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 - messengerA.destroy(); + + void messengerA.destroy(); expect(dispatchEventSpy).toHaveBeenCalledWith(expect.any(Event)); }); it("should trigger onDestroyListener when the destroy event is dispatched", async () => { const request = createRequest(); - // 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 - messengerA.request(request); + + void messengerA.request(request); const onDestroyListener = jest.fn(); (messengerA as any).onDestroy.addEventListener("destroy", onDestroyListener); - // 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 - messengerA.destroy(); + + void messengerA.destroy(); expect(onDestroyListener).toHaveBeenCalled(); const eventArg = onDestroyListener.mock.calls[0][0]; @@ -213,7 +207,7 @@ class MockMessagePort { remotePort: MockMessagePort; postMessage(message: T, port?: MessagePort) { - this.remotePort.onmessage( + this.remotePort.onmessage?.( new MessageEvent("message", { data: message, ports: port ? [port] : [], diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 8de48a49a8e..5818bbf8d82 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -6,7 +6,7 @@ import { filter, firstValueFrom, fromEvent, - fromEventPattern, + map, merge, Observable, Subject, @@ -28,6 +28,7 @@ import { import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import { fromChromeEvent } from "../../../platform/browser/from-chrome-event"; // FIXME (PM-22628): Popup imports are forbidden in background // eslint-disable-next-line no-restricted-imports import { closeFido2Popout, openFido2Popout } from "../../../vault/popup/utils/vault-popout-window"; @@ -154,9 +155,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi } static sendMessage(msg: BrowserFido2Message) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserApi.sendMessage(BrowserFido2MessageName, msg); + void BrowserApi.sendMessage(BrowserFido2MessageName, msg); } static abortPopout(sessionId: string, fallbackRequested = false) { @@ -205,9 +204,7 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi fromEvent(abortController.signal, "abort") .pipe(takeUntil(this.destroy$)) .subscribe(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.close(); + void this.close(); BrowserFido2UserInterfaceSession.sendMessage({ type: BrowserFido2MessageTypes.AbortRequest, sessionId: this.sessionId, @@ -223,21 +220,13 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi ) .subscribe((msg) => { if (msg.type === BrowserFido2MessageTypes.AbortResponse) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.close(); - // 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.abort(msg.fallbackRequested); + void this.close(); + void this.abort(msg.fallbackRequested); } }); - this.windowClosed$ = fromEventPattern( - // FIXME: Make sure that is does not cause a memory leak in Safari or use BrowserApi.AddListener - // and test that it doesn't break. Tracking Ticket: https://bitwarden.atlassian.net/browse/PM-4735 - // eslint-disable-next-line no-restricted-syntax - (handler: any) => chrome.windows.onRemoved.addListener(handler), - (handler: any) => chrome.windows.onRemoved.removeListener(handler), + this.windowClosed$ = fromChromeEvent(chrome.windows.onRemoved).pipe( + map(([windowId]) => windowId), ); BrowserFido2UserInterfaceSession.sendMessage({ @@ -391,12 +380,8 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi takeUntil(this.destroy$), ) .subscribe(() => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.close(); - // 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.abort(true); + void this.close(); + void this.abort(true); }); await connectPromise; diff --git a/apps/browser/src/autofill/models/autofill-field.ts b/apps/browser/src/autofill/models/autofill-field.ts index 1a8c3bb875b..9d2cf3773d4 100644 --- a/apps/browser/src/autofill/models/autofill-field.ts +++ b/apps/browser/src/autofill/models/autofill-field.ts @@ -1,6 +1,4 @@ import { FieldRect } from "../background/abstractions/overlay.background"; -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { AutofillFieldQualifierType } from "../enums/autofill-field.enums"; import { InlineMenuAccountCreationFieldTypes, @@ -13,34 +11,36 @@ import { export default class AutofillField { [key: string]: any; /** - * The unique identifier assigned to this field during collection of the page details + * Non-null asserted. The unique identifier assigned to this field during collection of the page details */ - opid: string; + opid!: string; /** - * Sequential number assigned to each element collected, based on its position in the DOM. + * Non-null asserted. Sequential number assigned to each element collected, based on its position in the DOM. * Used to do perform proximal checks for username and password fields on the DOM. */ - elementNumber: number; + elementNumber!: number; /** - * Designates whether the field is viewable on the current part of the DOM that the user can see + * Non-null asserted. Designates whether the field is viewable on the current part of the DOM that the user can see */ - viewable: boolean; + viewable!: boolean; /** - * The HTML `id` attribute of the field + * Non-null asserted. The HTML `id` attribute of the field */ - htmlID: string | null; + htmlID!: string | null; /** - * The HTML `name` attribute of the field + * Non-null asserted. The HTML `name` attribute of the field */ - htmlName: string | null; + htmlName!: string | null; /** - * The HTML `class` attribute of the field + * Non-null asserted. The HTML `class` attribute of the field */ - htmlClass: string | null; + htmlClass!: string | null; - tabindex: string | null; + /** Non-null asserted. */ + tabindex!: string | null; - title: string | null; + /** Non-null asserted. */ + title!: string | null; /** * The `tagName` for the field */ diff --git a/apps/browser/src/autofill/models/autofill-form.ts b/apps/browser/src/autofill/models/autofill-form.ts index d335a81b3c4..e9161620527 100644 --- a/apps/browser/src/autofill/models/autofill-form.ts +++ b/apps/browser/src/autofill/models/autofill-form.ts @@ -1,28 +1,31 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore /** * Represents an HTML form whose elements can be autofilled */ export default class AutofillForm { [key: string]: any; + /** - * The unique identifier assigned to this field during collection of the page details + * Non-null asserted. The unique identifier assigned to this field during collection of the page details */ - opid: string; + opid!: string; + /** - * The HTML `name` attribute of the form field + * Non-null asserted. The HTML `name` attribute of the form field */ - htmlName: string; + htmlName!: string; + /** - * The HTML `id` attribute of the form field + * Non-null asserted. The HTML `id` attribute of the form field */ - htmlID: string; + htmlID!: string; + /** - * The HTML `action` attribute of the form field + * Non-null asserted. The HTML `action` attribute of the form field */ - htmlAction: string; + htmlAction!: string; + /** - * The HTML `method` attribute of the form field + * Non-null asserted. The HTML `method` attribute of the form field. */ - htmlMethod: string; + htmlMethod!: "get" | "post" | string; } diff --git a/apps/browser/src/autofill/models/autofill-page-details.ts b/apps/browser/src/autofill/models/autofill-page-details.ts index c32dfed4e43..ca8c66a3152 100644 --- a/apps/browser/src/autofill/models/autofill-page-details.ts +++ b/apps/browser/src/autofill/models/autofill-page-details.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import AutofillField from "./autofill-field"; import AutofillForm from "./autofill-form"; @@ -7,16 +5,20 @@ import AutofillForm from "./autofill-form"; * The details of a page that have been collected and can be used for autofill */ export default class AutofillPageDetails { - title: string; - url: string; - documentUrl: string; + /** Non-null asserted. */ + title!: string; + /** Non-null asserted. */ + url!: string; + /** Non-null asserted. */ + documentUrl!: string; /** - * A collection of all of the forms in the page DOM, keyed by their `opid` + * Non-null asserted. A collection of all of the forms in the page DOM, keyed by their `opid` */ - forms: { [id: string]: AutofillForm }; + forms!: { [id: string]: AutofillForm }; /** - * A collection of all the fields in the page DOM, keyed by their `opid` + * Non-null asserted. A collection of all the fields in the page DOM, keyed by their `opid` */ - fields: AutofillField[]; - collectedTimestamp: number; + fields!: AutofillField[]; + /** Non-null asserted. */ + collectedTimestamp!: number; } diff --git a/apps/browser/src/autofill/models/autofill-script.ts b/apps/browser/src/autofill/models/autofill-script.ts index 1da05e07308..43c85c58c9a 100644 --- a/apps/browser/src/autofill/models/autofill-script.ts +++ b/apps/browser/src/autofill/models/autofill-script.ts @@ -1,26 +1,33 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -// String values affect code flow in autofill.ts and must not be changed -export type FillScriptActions = "click_on_opid" | "focus_by_opid" | "fill_by_opid"; - export type FillScript = [action: FillScriptActions, opid: string, value?: string]; export type AutofillScriptProperties = { delay_between_operations?: number; }; +export const FillScriptActionTypes = { + fill_by_opid: "fill_by_opid", + click_on_opid: "click_on_opid", + focus_by_opid: "focus_by_opid", +} as const; + +// String values affect code flow in autofill.ts and must not be changed +export type FillScriptActions = keyof typeof FillScriptActionTypes; + export type AutofillInsertActions = { - fill_by_opid: ({ opid, value }: { opid: string; value: string }) => void; - click_on_opid: ({ opid }: { opid: string }) => void; - focus_by_opid: ({ opid }: { opid: string }) => void; + [FillScriptActionTypes.fill_by_opid]: ({ opid, value }: { opid: string; value: string }) => void; + [FillScriptActionTypes.click_on_opid]: ({ opid }: { opid: string }) => void; + [FillScriptActionTypes.focus_by_opid]: ({ opid }: { opid: string }) => void; }; export default class AutofillScript { script: FillScript[] = []; properties: AutofillScriptProperties = {}; - metadata: any = {}; // Unused, not written or read - autosubmit: string[]; // Appears to be unused, read but not written - savedUrls: string[]; - untrustedIframe: boolean; - itemType: string; // Appears to be unused, read but not written + /** Non-null asserted. */ + autosubmit!: string[] | null; // Appears to be unused, read but not written + /** Non-null asserted. */ + savedUrls!: string[]; + /** Non-null asserted. */ + untrustedIframe!: boolean; + /** Non-null asserted. */ + itemType!: string; // Appears to be unused, read but not written } diff --git a/apps/browser/src/autofill/notification/bar.html b/apps/browser/src/autofill/notification/bar.html index c0b57de612e..8934fe6a031 100644 --- a/apps/browser/src/autofill/notification/bar.html +++ b/apps/browser/src/autofill/notification/bar.html @@ -1,5 +1,4 @@ - - + Bitwarden diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts index 4f497172b39..414673a9b81 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import "@webcomponents/custom-elements"; import "lit/polyfill-support.js"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -103,7 +101,10 @@ export class AutofillInlineMenuButton extends AutofillInlineMenuPageElement { */ private updatePageColorScheme({ colorScheme }: AutofillInlineMenuButtonMessage) { const colorSchemeMetaTag = globalThis.document.querySelector("meta[name='color-scheme']"); - colorSchemeMetaTag?.setAttribute("content", colorScheme); + + if (colorSchemeMetaTag && colorScheme) { + colorSchemeMetaTag.setAttribute("content", colorScheme); + } } /** diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss index 93f5f647ffe..ee9c68ee603 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss @@ -82,7 +82,7 @@ body * { width: 100%; font-family: $font-family-sans-serif; font-size: 1.6rem; - font-weight: 700; + font-weight: 500; text-align: left; background: transparent; border: none; @@ -187,7 +187,7 @@ body * { top: 0; z-index: 1; font-family: $font-family-sans-serif; - font-weight: 600; + font-weight: 500; font-size: 1rem; line-height: 1.3; letter-spacing: 0.025rem; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts index 663eae9144a..6d85982a1ac 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { setElementStyles } from "../../../../utils"; @@ -14,8 +12,10 @@ export class AutofillInlineMenuContainer { private readonly setElementStyles = setElementStyles; private readonly extensionOriginsSet: Set; private port: chrome.runtime.Port | null = null; - private portName: string; - private inlineMenuPageIframe: HTMLIFrameElement; + /** Non-null asserted. */ + private portName!: string; + /** Non-null asserted. */ + private inlineMenuPageIframe!: HTMLIFrameElement; private readonly iframeStyles: Partial = { all: "initial", position: "fixed", @@ -42,8 +42,10 @@ export class AutofillInlineMenuContainer { tabIndex: "-1", }; private readonly windowMessageHandlers: AutofillInlineMenuContainerWindowMessageHandlers = { - initAutofillInlineMenuButton: (message) => this.handleInitInlineMenuIframe(message), - initAutofillInlineMenuList: (message) => this.handleInitInlineMenuIframe(message), + initAutofillInlineMenuButton: (message: InitAutofillInlineMenuElementMessage) => + this.handleInitInlineMenuIframe(message), + initAutofillInlineMenuList: (message: InitAutofillInlineMenuElementMessage) => + this.handleInitInlineMenuIframe(message), }; constructor() { @@ -116,14 +118,20 @@ export class AutofillInlineMenuContainer { * * @param event - The message event. */ - private handleWindowMessage = (event: MessageEvent) => { + private handleWindowMessage = (event: MessageEvent) => { const message = event.data; if (this.isForeignWindowMessage(event)) { return; } - if (this.windowMessageHandlers[message.command]) { - this.windowMessageHandlers[message.command](message); + if ( + this.windowMessageHandlers[ + message.command as keyof AutofillInlineMenuContainerWindowMessageHandlers + ] + ) { + this.windowMessageHandlers[ + message.command as keyof AutofillInlineMenuContainerWindowMessageHandlers + ](message); return; } @@ -142,8 +150,8 @@ export class AutofillInlineMenuContainer { * * @param event - The message event. */ - private isForeignWindowMessage(event: MessageEvent) { - if (!event.data.portKey) { + private isForeignWindowMessage(event: MessageEvent) { + if (!event.data?.portKey) { return true; } @@ -159,7 +167,9 @@ export class AutofillInlineMenuContainer { * * @param event - The message event. */ - private isMessageFromParentWindow(event: MessageEvent): boolean { + private isMessageFromParentWindow( + event: MessageEvent, + ): boolean { return globalThis.parent === event.source; } @@ -168,7 +178,9 @@ export class AutofillInlineMenuContainer { * * @param event - The message event. */ - private isMessageFromInlineMenuPageIframe(event: MessageEvent): boolean { + private isMessageFromInlineMenuPageIframe( + event: MessageEvent, + ): boolean { if (!this.inlineMenuPageIframe) { return false; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts index 950676cf202..89f44a6a80d 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; import { RedirectFocusDirection } from "../../../../enums/autofill-overlay.enum"; @@ -10,10 +8,14 @@ import { export class AutofillInlineMenuPageElement extends HTMLElement { protected shadowDom: ShadowRoot; - protected messageOrigin: string; - protected translations: Record; - private portKey: string; - protected windowMessageHandlers: AutofillInlineMenuPageElementWindowMessageHandlers; + /** Non-null asserted. */ + protected messageOrigin!: string; + /** Non-null asserted. */ + protected translations!: Record; + /** Non-null asserted. */ + private portKey!: string; + /** Non-null asserted. */ + protected windowMessageHandlers!: AutofillInlineMenuPageElementWindowMessageHandlers; constructor() { super(); diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts index 45d29c1cda9..d5e8c559326 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts @@ -20,7 +20,7 @@ describe("OverlayNotificationsContentService", () => { beforeEach(() => { jest.useFakeTimers(); - jest.spyOn(utils, "sendExtensionMessage").mockImplementation(jest.fn()); + jest.spyOn(utils, "sendExtensionMessage").mockImplementation(async () => null); jest.spyOn(HTMLIFrameElement.prototype, "contentWindow", "get").mockReturnValue(window); postMessageSpy = jest.spyOn(window, "postMessage").mockImplementation(jest.fn()); domQueryService = mock(); diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts index f4c4c871478..9ccbce4d8e6 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; @@ -69,7 +67,7 @@ export class Fido2UseBrowserLinkComponent { this.platformUtilsService.showToast( "success", - null, + "", this.i18nService.t("domainAddedToExcludedDomains", validDomain), ); } diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html index add53a0cd33..1153ad58719 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.html +++ b/apps/browser/src/autofill/popup/settings/autofill.component.html @@ -232,7 +232,7 @@ {{ "enableAutoTotpCopy" | i18n }} - + {{ "clearClipboard" | i18n }} - + {{ "defaultUriMatchDetection" | i18n }} - - {{ "settingDisabledByPolicy" | i18n }} - {{ hints[0] | i18n }} diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index 62e5ba3a151..49be3104dc1 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -155,13 +155,15 @@ export class AutofillComponent implements OnInit { autofillOnPageLoadOptions: { name: string; value: boolean }[]; enableContextMenuItem: boolean = false; enableAutoTotpCopy: boolean = false; - clearClipboard: ClearClipboardDelaySetting; + /** Non-null asserted. */ + clearClipboard!: ClearClipboardDelaySetting; clearClipboardOptions: { name: string; value: ClearClipboardDelaySetting }[]; defaultUriMatch: UriMatchStrategySetting = UriMatchStrategy.Domain; uriMatchOptions: { name: string; value: UriMatchStrategySetting; disabled?: boolean }[]; showCardsCurrentTab: boolean = true; showIdentitiesCurrentTab: boolean = true; - autofillKeyboardHelperText: string; + /** Non-null asserted. */ + autofillKeyboardHelperText!: string; accountSwitcherEnabled: boolean = false; constructor( diff --git a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts index 56c2d1704d2..e1d24159664 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill-overlay-content.service.ts @@ -26,7 +26,7 @@ export type AutofillOverlayContentExtensionMessageHandlers = { destroyAutofillInlineMenuListeners: () => void; getInlineMenuFormFieldData: ({ message, - }: AutofillExtensionMessageParam) => Promise; + }: AutofillExtensionMessageParam) => Promise; }; export interface AutofillOverlayContentService { diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 09e22e278be..13a00fb573f 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Observable } from "rxjs"; import { UriMatchStrategySetting } from "@bitwarden/common/models/domain/domain-service"; @@ -64,29 +62,39 @@ export const COLLECT_PAGE_DETAILS_RESPONSE_COMMAND = ); export abstract class AutofillService { - collectPageDetailsFromTab$: (tab: chrome.tabs.Tab) => Observable; - loadAutofillScriptsOnInstall: () => Promise; - reloadAutofillScripts: () => Promise; - injectAutofillScripts: ( + /** Non-null asserted. */ + collectPageDetailsFromTab$!: (tab: chrome.tabs.Tab) => Observable; + /** Non-null asserted. */ + loadAutofillScriptsOnInstall!: () => Promise; + /** Non-null asserted. */ + reloadAutofillScripts!: () => Promise; + /** Non-null asserted. */ + injectAutofillScripts!: ( tab: chrome.tabs.Tab, frameId?: number, triggeringOnPageLoad?: boolean, ) => Promise; - getFormsWithPasswordFields: (pageDetails: AutofillPageDetails) => FormData[]; - doAutoFill: (options: AutoFillOptions) => Promise; - doAutoFillOnTab: ( + /** Non-null asserted. */ + getFormsWithPasswordFields!: (pageDetails: AutofillPageDetails) => FormData[]; + /** Non-null asserted. */ + doAutoFill!: (options: AutoFillOptions) => Promise; + /** Non-null asserted. */ + doAutoFillOnTab!: ( pageDetails: PageDetail[], tab: chrome.tabs.Tab, fromCommand: boolean, autoSubmitLogin?: boolean, ) => Promise; - doAutoFillActiveTab: ( + /** Non-null asserted. */ + doAutoFillActiveTab!: ( pageDetails: PageDetail[], fromCommand: boolean, cipherType?: CipherType, ) => Promise; - setAutoFillOnPageLoadOrgPolicy: () => Promise; - isPasswordRepromptRequired: ( + /** Non-null asserted. */ + setAutoFillOnPageLoadOrgPolicy!: () => Promise; + /** Non-null asserted. */ + isPasswordRepromptRequired!: ( cipher: CipherView, tab: chrome.tabs.Tab, action?: string, diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts index 96b05b81c96..3c19589afef 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.spec.ts @@ -32,6 +32,17 @@ import { InlineMenuFieldQualificationService } from "./inline-menu-field-qualifi const defaultWindowReadyState = document.readyState; const defaultDocumentVisibilityState = document.visibilityState; + +const mockRect = (rect: { left: number; top: number; width: number; height: number }) => + ({ + ...rect, + x: rect.left, + y: rect.top, + right: rect.left + rect.width, + bottom: rect.top + rect.height, + toJSON: () => ({}), + }) as DOMRectReadOnly; + describe("AutofillOverlayContentService", () => { let domQueryService: DomQueryService; let domElementVisibilityService: DomElementVisibilityService; @@ -2154,6 +2165,10 @@ describe("AutofillOverlayContentService", () => { }); it("calculates the sub frame's offsets if a single frame with the referenced url exists", async () => { + const iframe = document.querySelector("iframe") as HTMLIFrameElement; + jest + .spyOn(iframe, "getBoundingClientRect") + .mockReturnValue(mockRect({ left: 0, top: 0, width: 1, height: 1 })); sendMockExtensionMessage( { command: "getSubFrameOffsets", @@ -2270,6 +2285,9 @@ describe("AutofillOverlayContentService", () => { }); document.body.innerHTML = ``; const iframe = document.querySelector("iframe") as HTMLIFrameElement; + jest + .spyOn(iframe, "getBoundingClientRect") + .mockReturnValue(mockRect({ width: 1, height: 1, left: 2, top: 2 })); const subFrameData = { url: "https://example.com/", frameId: 10, @@ -2305,6 +2323,9 @@ describe("AutofillOverlayContentService", () => { it("posts the calculated sub frame data to the background", async () => { document.body.innerHTML = ``; const iframe = document.querySelector("iframe") as HTMLIFrameElement; + jest + .spyOn(iframe, "getBoundingClientRect") + .mockReturnValue(mockRect({ width: 1, height: 1, left: 2, top: 2 })); const subFrameData = { url: "https://example.com/", frameId: 10, @@ -2335,6 +2356,39 @@ describe("AutofillOverlayContentService", () => { }); }); + describe("calculateSubFrameOffsets", () => { + it("returns null when iframe has zero width and height", () => { + const iframe = document.querySelector("iframe") as HTMLIFrameElement; + + jest + .spyOn(iframe, "getBoundingClientRect") + .mockReturnValue(mockRect({ left: 0, top: 0, width: 0, height: 0 })); + + const result = autofillOverlayContentService["calculateSubFrameOffsets"]( + iframe, + "https://example.com/", + 10, + ); + + expect(result).toBeNull(); + }); + + it("returns null when iframe is not connected to the document", () => { + const iframe = document.createElement("iframe") as HTMLIFrameElement; + + jest + .spyOn(iframe, "getBoundingClientRect") + .mockReturnValue(mockRect({ width: 100, height: 50, left: 10, top: 20 })); + + const result = autofillOverlayContentService["calculateSubFrameOffsets"]( + iframe, + "https://example.com/", + 10, + ); + expect(result).toBeNull(); + }); + }); + describe("checkMostRecentlyFocusedFieldHasValue message handler", () => { it("returns true if the most recently focused field has a truthy value", async () => { autofillOverlayContentService["mostRecentlyFocusedField"] = mock< 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 656516d1119..7c98859070a 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1485,12 +1485,17 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ frameId?: number, ): SubFrameOffsetData { const iframeRect = iframeElement.getBoundingClientRect(); + const iframeRectHasSize = iframeRect.width > 0 && iframeRect.height > 0; const iframeStyles = globalThis.getComputedStyle(iframeElement); const paddingLeft = parseInt(iframeStyles.getPropertyValue("padding-left")) || 0; const paddingTop = parseInt(iframeStyles.getPropertyValue("padding-top")) || 0; const borderWidthLeft = parseInt(iframeStyles.getPropertyValue("border-left-width")) || 0; const borderWidthTop = parseInt(iframeStyles.getPropertyValue("border-top-width")) || 0; + if (!iframeRect || !iframeRectHasSize || !iframeElement.isConnected) { + return null; + } + return { url: subFrameUrl, frameId, @@ -1525,6 +1530,10 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ subFrameData.frameId, ); + if (!subFrameOffsets) { + return; + } + subFrameData.top += subFrameOffsets.top; subFrameData.left += subFrameOffsets.left; @@ -1657,10 +1666,6 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ globalThis.addEventListener(EVENTS.RESIZE, repositionHandler); } - private shouldRepositionSubFrameInlineMenuOnScroll = async () => { - return await this.sendExtensionMessage("shouldRepositionSubFrameInlineMenuOnScroll"); - }; - /** * Removes the listeners that facilitate repositioning * the overlay elements on scroll or resize. diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 77e8c661d08..bfeaa360a39 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -369,9 +369,7 @@ describe("AutofillService", () => { jest.spyOn(autofillService as any, "injectAutofillScriptsInAllTabs"); jest.spyOn(autofillService, "getAutofillOnPageLoad").mockResolvedValue(true); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - autofillService.reloadAutofillScripts(); + void autofillService.reloadAutofillScripts(); expect(port1.disconnect).toHaveBeenCalled(); expect(port2.disconnect).toHaveBeenCalled(); @@ -680,7 +678,9 @@ describe("AutofillService", () => { await autofillService.doAutoFill(autofillOptions); triggerTestFailure(); } catch (error) { - expect(error.message).toBe(nothingToAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(nothingToAutofillError); + } } }); @@ -691,7 +691,9 @@ describe("AutofillService", () => { await autofillService.doAutoFill(autofillOptions); triggerTestFailure(); } catch (error) { - expect(error.message).toBe(nothingToAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(nothingToAutofillError); + } } }); @@ -702,7 +704,9 @@ describe("AutofillService", () => { await autofillService.doAutoFill(autofillOptions); triggerTestFailure(); } catch (error) { - expect(error.message).toBe(nothingToAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(nothingToAutofillError); + } } }); @@ -713,7 +717,9 @@ describe("AutofillService", () => { await autofillService.doAutoFill(autofillOptions); triggerTestFailure(); } catch (error) { - expect(error.message).toBe(nothingToAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(nothingToAutofillError); + } } }); @@ -727,7 +733,9 @@ describe("AutofillService", () => { await autofillService.doAutoFill(autofillOptions); triggerTestFailure(); } catch (error) { - expect(error.message).toBe(didNotAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(didNotAutofillError); + } } }); }); @@ -766,7 +774,6 @@ describe("AutofillService", () => { { command: "fillForm", fillScript: { - metadata: {}, properties: { delay_between_operations: 20, }, @@ -863,7 +870,9 @@ describe("AutofillService", () => { expect(logService.info).toHaveBeenCalledWith( "Autofill on page load was blocked due to an untrusted iframe.", ); - expect(error.message).toBe(didNotAutofillError); + if (error instanceof Error) { + expect(error.message).toBe(didNotAutofillError); + } } }); @@ -898,7 +907,10 @@ describe("AutofillService", () => { } catch (error) { expect(autofillService["generateFillScript"]).toHaveBeenCalled(); expect(BrowserApi.tabSendMessage).not.toHaveBeenCalled(); - expect(error.message).toBe(didNotAutofillError); + + if (error instanceof Error) { + expect(error.message).toBe(didNotAutofillError); + } } }); @@ -1370,7 +1382,10 @@ describe("AutofillService", () => { triggerTestFailure(); } catch (error) { expect(BrowserApi.getTabFromCurrentWindow).toHaveBeenCalled(); - expect(error.message).toBe("No tab found."); + + if (error instanceof Error) { + expect(error.message).toBe("No tab found."); + } } }); @@ -1610,7 +1625,6 @@ describe("AutofillService", () => { expect(autofillService["generateLoginFillScript"]).toHaveBeenCalledWith( { - metadata: {}, properties: {}, script: [ ["click_on_opid", "username-field"], @@ -1648,7 +1662,6 @@ describe("AutofillService", () => { expect(autofillService["generateCardFillScript"]).toHaveBeenCalledWith( { - metadata: {}, properties: {}, script: [ ["click_on_opid", "username-field"], @@ -1686,7 +1699,6 @@ describe("AutofillService", () => { expect(autofillService["generateIdentityFillScript"]).toHaveBeenCalledWith( { - metadata: {}, properties: {}, script: [ ["click_on_opid", "username-field"], @@ -2279,7 +2291,7 @@ describe("AutofillService", () => { ); expect(value).toStrictEqual({ autosubmit: null, - metadata: {}, + itemType: "", properties: { delay_between_operations: 20 }, savedUrls: ["https://www.example.com"], script: [ @@ -2294,7 +2306,6 @@ describe("AutofillService", () => { ["fill_by_opid", "password", "password"], ["focus_by_opid", "password"], ], - itemType: "", untrustedIframe: false, }); }); @@ -2364,11 +2375,10 @@ describe("AutofillService", () => { describe("given an invalid autofill field", () => { const unmodifiedFillScriptValues: AutofillScript = { autosubmit: null, - metadata: {}, + itemType: "", properties: { delay_between_operations: 20 }, savedUrls: [], script: [], - itemType: "", untrustedIframe: false, }; @@ -2555,7 +2565,6 @@ describe("AutofillService", () => { expect(value).toStrictEqual({ autosubmit: null, itemType: "", - metadata: {}, properties: { delay_between_operations: 20, }, diff --git a/apps/browser/src/autofill/services/dom-element-visibility.service.ts b/apps/browser/src/autofill/services/dom-element-visibility.service.ts index bd75cb55ba5..21f024a510c 100644 --- a/apps/browser/src/autofill/services/dom-element-visibility.service.ts +++ b/apps/browser/src/autofill/services/dom-element-visibility.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { AutofillInlineMenuContentService } from "../overlay/inline-menu/abstractions/autofill-inline-menu-content.service"; import { FillableFormFieldElement, FormFieldElement } from "../types"; @@ -202,7 +200,7 @@ class DomElementVisibilityService implements DomElementVisibilityServiceInterfac const closestParentLabel = elementAtCenterPoint?.parentElement?.closest("label"); - return targetElementLabelsSet.has(closestParentLabel); + return closestParentLabel ? targetElementLabelsSet.has(closestParentLabel) : false; } } diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index b681e8e9fbb..1b0c5681ff0 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS, MAX_DEEP_QUERY_RECURSION_DEPTH } from "@bitwarden/common/autofill/constants"; import { nodeIsElement } from "../utils"; @@ -7,7 +5,8 @@ import { nodeIsElement } from "../utils"; import { DomQueryService as DomQueryServiceInterface } from "./abstractions/dom-query.service"; export class DomQueryService implements DomQueryServiceInterface { - private pageContainsShadowDom: boolean; + /** Non-null asserted. */ + private pageContainsShadowDom!: boolean; private ignoredTreeWalkerNodes = new Set([ "svg", "script", @@ -217,13 +216,12 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (error) { + } catch { return null; } } + // Firefox-specific equivalent of `openOrClosedShadowRoot` return (node as any).openOrClosedShadowRoot; } @@ -276,7 +274,7 @@ export class DomQueryService implements DomQueryServiceInterface { ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT, ); - let currentNode = treeWalker?.currentNode; + let currentNode: Node | null = treeWalker?.currentNode; while (currentNode) { if (filterCallback(currentNode)) { diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index ed8e41df8ba..f7c46a9fa77 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; import { getSubmitButtonKeywordsSet, sendExtensionMessage } from "../utils"; @@ -162,12 +160,14 @@ export class InlineMenuFieldQualificationService private isExplicitIdentityEmailField(field: AutofillField): boolean { const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder]; for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) { - if (!matchFieldAttributeValues[attrIndex]) { + const attributeValueToMatch = matchFieldAttributeValues[attrIndex]; + + if (!attributeValueToMatch) { continue; } for (let keywordIndex = 0; keywordIndex < matchFieldAttributeValues.length; keywordIndex++) { - if (this.newEmailFieldKeywords.has(matchFieldAttributeValues[attrIndex])) { + if (this.newEmailFieldKeywords.has(attributeValueToMatch)) { return true; } } @@ -210,10 +210,7 @@ export class InlineMenuFieldQualificationService } constructor() { - void Promise.all([ - sendExtensionMessage("getInlineMenuFieldQualificationFeatureFlag"), - sendExtensionMessage("getUserPremiumStatus"), - ]).then(([fieldQualificationFlag, premiumStatus]) => { + void sendExtensionMessage("getUserPremiumStatus").then((premiumStatus) => { this.premiumEnabled = !!premiumStatus?.result; }); } @@ -263,7 +260,13 @@ export class InlineMenuFieldQualificationService return true; } - const parentForm = pageDetails.forms[field.form]; + let parentForm; + + const fieldForm = field.form; + + if (fieldForm) { + parentForm = pageDetails.forms[fieldForm]; + } // If the field does not have a parent form if (!parentForm) { @@ -321,7 +324,13 @@ export class InlineMenuFieldQualificationService return false; } - const parentForm = pageDetails.forms[field.form]; + let parentForm; + + const fieldForm = field.form; + + if (fieldForm) { + parentForm = pageDetails.forms[fieldForm]; + } if (!parentForm) { // If the field does not have a parent form, but we can identify that the page contains at least @@ -374,7 +383,13 @@ export class InlineMenuFieldQualificationService field: AutofillField, pageDetails: AutofillPageDetails, ): boolean { - const parentForm = pageDetails.forms[field.form]; + let parentForm; + + const fieldForm = field.form; + + if (fieldForm) { + parentForm = pageDetails.forms[fieldForm]; + } // If the provided field is set with an autocomplete value of "current-password", we should assume that // the page developer intends for this field to be interpreted as a password field for a login form. @@ -476,7 +491,13 @@ export class InlineMenuFieldQualificationService // If the field is not explicitly set as a username field, we need to qualify // the field based on the other fields that are present on the page. - const parentForm = pageDetails.forms[field.form]; + let parentForm; + + const fieldForm = field.form; + + if (fieldForm) { + parentForm = pageDetails.forms[fieldForm]; + } const passwordFieldsInPageDetails = pageDetails.fields.filter(this.isCurrentPasswordField); if (this.isNewsletterForm(parentForm)) { @@ -919,8 +940,10 @@ export class InlineMenuFieldQualificationService * @param field - The field to validate */ isUsernameField = (field: AutofillField): boolean => { + const fieldType = field.type; if ( - !this.usernameFieldTypes.has(field.type) || + !fieldType || + !this.usernameFieldTypes.has(fieldType) || this.isExcludedFieldType(field, this.excludedAutofillFieldTypesSet) || this.fieldHasDisqualifyingAttributeValue(field) ) { @@ -1026,7 +1049,13 @@ export class InlineMenuFieldQualificationService const testedValues = [field.htmlID, field.htmlName, field.placeholder]; for (let i = 0; i < testedValues.length; i++) { - if (this.valueIsLikePassword(testedValues[i])) { + const attributeValueToMatch = testedValues[i]; + + if (!attributeValueToMatch) { + continue; + } + + if (this.valueIsLikePassword(attributeValueToMatch)) { return true; } } @@ -1101,7 +1130,9 @@ export class InlineMenuFieldQualificationService * @param excludedTypes - The set of excluded types */ private isExcludedFieldType(field: AutofillField, excludedTypes: Set): boolean { - if (excludedTypes.has(field.type)) { + const fieldType = field.type; + + if (fieldType && excludedTypes.has(fieldType)) { return true; } @@ -1116,12 +1147,14 @@ export class InlineMenuFieldQualificationService private isSearchField(field: AutofillField): boolean { const matchFieldAttributeValues = [field.type, field.htmlName, field.htmlID, field.placeholder]; for (let attrIndex = 0; attrIndex < matchFieldAttributeValues.length; attrIndex++) { - if (!matchFieldAttributeValues[attrIndex]) { + const attributeValueToMatch = matchFieldAttributeValues[attrIndex]; + + if (!attributeValueToMatch) { continue; } // Separate camel case words and case them to lower case values - const camelCaseSeparatedFieldAttribute = matchFieldAttributeValues[attrIndex] + const camelCaseSeparatedFieldAttribute = attributeValueToMatch .replace(/([a-z])([A-Z])/g, "$1 $2") .toLowerCase(); // Split the attribute by non-alphabetical characters to get the keywords @@ -1168,7 +1201,7 @@ export class InlineMenuFieldQualificationService this.submitButtonKeywordsMap.set(element, Array.from(keywordsSet).join(",")); } - return this.submitButtonKeywordsMap.get(element); + return this.submitButtonKeywordsMap.get(element) || ""; } /** @@ -1222,8 +1255,9 @@ export class InlineMenuFieldQualificationService ]; const keywordsSet = new Set(); for (let i = 0; i < keywords.length; i++) { - if (keywords[i] && typeof keywords[i] === "string") { - let keywordEl = keywords[i].toLowerCase(); + const attributeValue = keywords[i]; + if (attributeValue && typeof attributeValue === "string") { + let keywordEl = attributeValue.toLowerCase(); keywordsSet.add(keywordEl); // Remove hyphens from all potential keywords, we want to treat these as a single word. @@ -1253,7 +1287,7 @@ export class InlineMenuFieldQualificationService } const mapValues = this.autofillFieldKeywordsMap.get(autofillFieldData); - return returnStringValue ? mapValues.stringValue : mapValues.keywordsSet; + return mapValues ? (returnStringValue ? mapValues.stringValue : mapValues.keywordsSet) : ""; } /** diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts index 63cd4b534fb..1f2b23021f4 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.spec.ts @@ -2,7 +2,7 @@ import { mock } from "jest-mock-extended"; import { EVENTS } from "@bitwarden/common/autofill/constants"; -import AutofillScript, { FillScript, FillScriptActions } from "../models/autofill-script"; +import AutofillScript, { FillScript, FillScriptActionTypes } from "../models/autofill-script"; import { mockQuerySelectorAllDefinedCall } from "../spec/testing-utils"; import { FillableFormFieldElement, FormElementWithAttribute, FormFieldElement } from "../types"; @@ -94,14 +94,13 @@ describe("InsertAutofillContentService", () => { ); fillScript = { script: [ - ["click_on_opid", "username"], - ["focus_by_opid", "username"], - ["fill_by_opid", "username", "test"], + [FillScriptActionTypes.click_on_opid, "username"], + [FillScriptActionTypes.focus_by_opid, "username"], + [FillScriptActionTypes.fill_by_opid, "username", "test"], ], properties: { delay_between_operations: 20, }, - metadata: {}, autosubmit: [], savedUrls: ["https://bitwarden.com"], untrustedIframe: false, @@ -221,17 +220,14 @@ describe("InsertAutofillContentService", () => { expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith( 1, fillScript.script[0], - 0, ); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith( 2, fillScript.script[1], - 1, ); expect(insertAutofillContentService["runFillScriptAction"]).toHaveBeenNthCalledWith( 3, fillScript.script[2], - 2, ); }); }); @@ -376,42 +372,62 @@ describe("InsertAutofillContentService", () => { }); it("returns early if no opid is provided", async () => { - const action = "fill_by_opid"; + const action = FillScriptActionTypes.fill_by_opid; const opid = ""; const value = "value"; const scriptAction: FillScript = [action, opid, value]; jest.spyOn(insertAutofillContentService["autofillInsertActions"], action); - await insertAutofillContentService["runFillScriptAction"](scriptAction, 0); + await insertAutofillContentService["runFillScriptAction"](scriptAction); jest.advanceTimersByTime(20); expect(insertAutofillContentService["autofillInsertActions"][action]).not.toHaveBeenCalled(); }); describe("given a valid fill script action and opid", () => { - const fillScriptActions: FillScriptActions[] = [ - "fill_by_opid", - "click_on_opid", - "focus_by_opid", - ]; - fillScriptActions.forEach((action) => { - it(`triggers a ${action} action`, () => { - const opid = "opid"; - const value = "value"; - const scriptAction: FillScript = [action, opid, value]; - jest.spyOn(insertAutofillContentService["autofillInsertActions"], action); + it(`triggers a fill_by_opid action`, () => { + const action = FillScriptActionTypes.fill_by_opid; + const opid = "opid"; + const value = "value"; + const scriptAction: FillScript = [action, opid, value]; + jest.spyOn(insertAutofillContentService["autofillInsertActions"], action); - // 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 - insertAutofillContentService["runFillScriptAction"](scriptAction, 0); - jest.advanceTimersByTime(20); + void insertAutofillContentService["runFillScriptAction"](scriptAction); + jest.advanceTimersByTime(20); - expect( - insertAutofillContentService["autofillInsertActions"][action], - ).toHaveBeenCalledWith({ - opid, - value, - }); + expect(insertAutofillContentService["autofillInsertActions"][action]).toHaveBeenCalledWith({ + opid, + value, + }); + }); + + it(`triggers a click_on_opid action`, () => { + const action = FillScriptActionTypes.click_on_opid; + const opid = "opid"; + const value = "value"; + const scriptAction: FillScript = [action, opid, value]; + jest.spyOn(insertAutofillContentService["autofillInsertActions"], action); + + void insertAutofillContentService["runFillScriptAction"](scriptAction); + jest.advanceTimersByTime(20); + + expect(insertAutofillContentService["autofillInsertActions"][action]).toHaveBeenCalledWith({ + opid, + }); + }); + + it(`triggers a focus_by_opid action`, () => { + const action = FillScriptActionTypes.focus_by_opid; + const opid = "opid"; + const value = "value"; + const scriptAction: FillScript = [action, opid, value]; + jest.spyOn(insertAutofillContentService["autofillInsertActions"], action); + + void insertAutofillContentService["runFillScriptAction"](scriptAction); + jest.advanceTimersByTime(20); + + expect(insertAutofillContentService["autofillInsertActions"][action]).toHaveBeenCalledWith({ + opid, }); }); }); diff --git a/apps/browser/src/autofill/services/insert-autofill-content.service.ts b/apps/browser/src/autofill/services/insert-autofill-content.service.ts index 6c951afc1a0..4b7f699fecb 100644 --- a/apps/browser/src/autofill/services/insert-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/insert-autofill-content.service.ts @@ -1,8 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { EVENTS, TYPE_CHECK } from "@bitwarden/common/autofill/constants"; -import AutofillScript, { AutofillInsertActions, FillScript } from "../models/autofill-script"; +import AutofillScript, { + AutofillInsertActions, + FillScript, + FillScriptActionTypes, +} from "../models/autofill-script"; import { FormFieldElement } from "../types"; import { currentlyInSandboxedIframe, @@ -50,7 +52,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf } for (let index = 0; index < fillScript.script.length; index++) { - await this.runFillScriptAction(fillScript.script[index], index); + await this.runFillScriptAction(fillScript.script[index]); } } @@ -116,25 +118,26 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf /** * Runs the autofill action based on the action type and the opid. * Each action is subsequently delayed by 20 milliseconds. - * @param {"click_on_opid" | "focus_by_opid" | "fill_by_opid"} action - * @param {string} opid - * @param {string} value - * @param {number} actionIndex + * @param {FillScript} [action, opid, value] * @returns {Promise} * @private */ - private runFillScriptAction = ( - [action, opid, value]: FillScript, - actionIndex: number, - ): Promise => { + private runFillScriptAction = ([action, opid, value]: FillScript): Promise => { if (!opid || !this.autofillInsertActions[action]) { - return; + return Promise.resolve(); } const delayActionsInMilliseconds = 20; return new Promise((resolve) => setTimeout(() => { - this.autofillInsertActions[action]({ opid, value }); + if (action === FillScriptActionTypes.fill_by_opid && !!value?.length) { + this.autofillInsertActions.fill_by_opid({ opid, value }); + } else if (action === FillScriptActionTypes.click_on_opid) { + this.autofillInsertActions.click_on_opid({ opid }); + } else if (action === FillScriptActionTypes.focus_by_opid) { + this.autofillInsertActions.focus_by_opid({ opid }); + } + resolve(); }, delayActionsInMilliseconds), ); @@ -158,7 +161,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf */ private handleClickOnFieldByOpidAction(opid: string) { const element = this.collectAutofillContentService.getAutofillFieldElementByOpid(opid); - this.triggerClickOnElement(element); + + if (element) { + this.triggerClickOnElement(element); + } } /** @@ -171,6 +177,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf private handleFocusOnFieldByOpidAction(opid: string) { const element = this.collectAutofillContentService.getAutofillFieldElementByOpid(opid); + if (!element) { + return; + } + if (document.activeElement === element) { element.blur(); } @@ -187,6 +197,10 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf * @private */ private insertValueIntoField(element: FormFieldElement | null, value: string) { + if (!element || !value) { + return; + } + const elementCanBeReadonly = elementIsInputElement(element) || elementIsTextAreaElement(element); const elementCanBeFilled = elementCanBeReadonly || elementIsSelectElement(element); @@ -195,8 +209,6 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf const elementAlreadyHasTheValue = !!(elementValue?.length && elementValue === value); if ( - !element || - !value || elementAlreadyHasTheValue || (elementCanBeReadonly && element.readOnly) || (elementCanBeFilled && element.disabled) @@ -298,7 +310,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf * @private */ private triggerClickOnElement(element?: HTMLElement): void { - if (typeof element?.click !== TYPE_CHECK.FUNCTION) { + if (!element || typeof element.click !== TYPE_CHECK.FUNCTION) { return; } @@ -313,7 +325,7 @@ class InsertAutofillContentService implements InsertAutofillContentServiceInterf * @private */ private triggerFocusOnElement(element: HTMLElement | undefined, shouldResetValue = false): void { - if (typeof element?.focus !== TYPE_CHECK.FUNCTION) { + if (!element || typeof element.focus !== TYPE_CHECK.FUNCTION) { return; } diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index 1e804ed8fd2..f356eb86f3a 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; $font-size-base: 14px; diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index d1e127227c6..3714ef2105b 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { mock } from "jest-mock-extended"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -144,7 +142,6 @@ export function createAutofillScriptMock( return { autosubmit: null, - metadata: {}, properties: { delay_between_operations: 20, }, @@ -299,7 +296,7 @@ export function createMutationRecordMock(customFields = {}): MutationRecord { oldValue: "default-oldValue", previousSibling: null, removedNodes: mock(), - target: null, + target: mock(), type: "attributes", ...customFields, }; diff --git a/apps/browser/src/background/commands.background.ts b/apps/browser/src/background/commands.background.ts index 3e6e86cd3d7..696fd5c4f05 100644 --- a/apps/browser/src/background/commands.background.ts +++ b/apps/browser/src/background/commands.background.ts @@ -1,9 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { firstValueFrom } from "rxjs"; + +import { LockService } 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"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ExtensionCommand, ExtensionCommandType } from "@bitwarden/common/autofill/constants"; -import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; // FIXME (PM-22628): Popup imports are forbidden in background @@ -21,9 +25,10 @@ export default class CommandsBackground { constructor( private main: MainBackground, private platformUtilsService: PlatformUtilsService, - private vaultTimeoutService: VaultTimeoutService, private authService: AuthService, private generatePasswordToClipboard: () => Promise, + private accountService: AccountService, + private lockService: LockService, ) { this.isSafari = this.platformUtilsService.isSafari(); this.isVivaldi = this.platformUtilsService.isVivaldi(); @@ -72,9 +77,11 @@ export default class CommandsBackground { case "open_popup": await this.openPopup(); break; - case "lock_vault": - await this.vaultTimeoutService.lock(); + case "lock_vault": { + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.lockService.lock(activeUserId); break; + } default: break; } diff --git a/apps/browser/src/background/idle.background.ts b/apps/browser/src/background/idle.background.ts index 0f89aa4792a..66a5604a8ba 100644 --- a/apps/browser/src/background/idle.background.ts +++ b/apps/browser/src/background/idle.background.ts @@ -1,6 +1,6 @@ import { firstValueFrom } from "rxjs"; -import { LogoutService } from "@bitwarden/auth/common"; +import { LockService, LogoutService } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { VaultTimeoutAction, @@ -23,6 +23,7 @@ export default class IdleBackground { private serverNotificationsService: ServerNotificationsService, private accountService: AccountService, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, + private lockService: LockService, private logoutService: LogoutService, ) { this.idle = chrome.idle || (browser != null ? browser.idle : null); @@ -66,7 +67,7 @@ export default class IdleBackground { if (action === VaultTimeoutAction.LogOut) { await this.logoutService.logout(userId as UserId, "vaultTimeout"); } else { - await this.vaultTimeoutService.lock(userId); + await this.lockService.lock(userId as UserId); } } } diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 561ad5e9c9e..97bfe804411 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -20,9 +20,9 @@ import { AuthRequestService, AuthRequestServiceAbstraction, DefaultAuthRequestApiService, - DefaultLockService, DefaultLogoutService, InternalUserDecryptionOptionsServiceAbstraction, + LockService, LoginEmailServiceAbstraction, LogoutReason, UserDecryptionOptionsService, @@ -270,6 +270,7 @@ import { } from "@bitwarden/vault-export-core"; import { AuthStatusBadgeUpdaterService } from "../auth/services/auth-status-badge-updater.service"; +import { ExtensionLockService } from "../auth/services/extension-lock.service"; import { OverlayNotificationsBackground as OverlayNotificationsBackgroundInterface } from "../autofill/background/abstractions/overlay-notifications.background"; import { OverlayBackground as OverlayBackgroundInterface } from "../autofill/background/abstractions/overlay.background"; import { AutoSubmitLoginBackground } from "../autofill/background/auto-submit-login.background"; @@ -293,6 +294,7 @@ import { AutofillBadgeUpdaterService } from "../autofill/services/autofill-badge import AutofillService from "../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../autofill/services/inline-menu-field-qualification.service"; import { SafariApp } from "../browser/safariApp"; +import { PhishingDataService } from "../dirt/phishing-detection/services/phishing-data.service"; import { PhishingDetectionService } from "../dirt/phishing-detection/services/phishing-detection.service"; import { BackgroundBrowserBiometricsService } from "../key-management/biometrics/background-browser-biometrics.service"; import VaultTimeoutService from "../key-management/vault-timeout/vault-timeout.service"; @@ -362,6 +364,7 @@ export default class MainBackground { folderService: InternalFolderServiceAbstraction; userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction; collectionService: CollectionService; + lockService: LockService; vaultTimeoutService?: VaultTimeoutService; vaultTimeoutSettingsService: VaultTimeoutSettingsService; passwordGenerationService: PasswordGenerationServiceAbstraction; @@ -491,17 +494,10 @@ export default class MainBackground { private popupViewCacheBackgroundService: PopupViewCacheBackgroundService; private popupRouterCacheBackgroundService: PopupRouterCacheBackgroundService; - constructor() { - // Services - const lockedCallback = async (userId: UserId) => { - await this.refreshMenu(true); - if (this.systemService != null) { - await this.systemService.clearPendingClipboard(); - await this.biometricsService.setShouldAutopromptNow(false); - await this.processReloadService.startProcessReload(this.authService); - } - }; + // DIRT + private phishingDataService: PhishingDataService; + constructor() { const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) => await this.logout(logoutReason, userId); @@ -983,27 +979,6 @@ export default class MainBackground { this.restrictedItemTypesService, ); - const logoutService = new DefaultLogoutService(this.messagingService); - this.vaultTimeoutService = new VaultTimeoutService( - this.accountService, - this.masterPasswordService, - this.cipherService, - this.folderService, - this.collectionService, - this.platformUtilsService, - this.messagingService, - this.searchService, - this.stateService, - this.tokenService, - this.authService, - this.vaultTimeoutSettingsService, - this.stateEventRunnerService, - this.taskSchedulerService, - this.logService, - this.biometricsService, - lockedCallback, - logoutService, - ); this.containerService = new ContainerService(this.keyService, this.encryptService); this.sendStateProvider = new SendStateProvider(this.stateProvider); @@ -1267,6 +1242,7 @@ export default class MainBackground { this.biometricStateService, this.accountService, this.logService, + this.authService, ); // Background @@ -1280,7 +1256,36 @@ export default class MainBackground { this.authService, ); - const lockService = new DefaultLockService(this.accountService, this.vaultTimeoutService); + const logoutService = new DefaultLogoutService(this.messagingService); + this.lockService = new ExtensionLockService( + this.accountService, + this.biometricsService, + this.vaultTimeoutSettingsService, + logoutService, + this.messagingService, + this.searchService, + this.folderService, + this.masterPasswordService, + this.stateEventRunnerService, + this.cipherService, + this.authService, + this.systemService, + this.processReloadService, + this.logService, + this.keyService, + this, + ); + + this.vaultTimeoutService = new VaultTimeoutService( + this.accountService, + this.platformUtilsService, + this.authService, + this.vaultTimeoutSettingsService, + this.taskSchedulerService, + this.logService, + this.lockService, + logoutService, + ); this.runtimeBackground = new RuntimeBackground( this, @@ -1294,7 +1299,7 @@ export default class MainBackground { this.configService, messageListener, this.accountService, - lockService, + this.lockService, this.billingAccountProfileStateService, this.browserInitialInstallService, ); @@ -1314,9 +1319,10 @@ export default class MainBackground { this.commandsBackground = new CommandsBackground( this, this.platformUtilsService, - this.vaultTimeoutService, this.authService, () => this.generatePasswordToClipboard(), + this.accountService, + this.lockService, ); this.taskService = new DefaultTaskService( @@ -1401,6 +1407,7 @@ export default class MainBackground { this.serverNotificationsService, this.accountService, this.vaultTimeoutSettingsService, + this.lockService, logoutService, ); @@ -1451,15 +1458,21 @@ export default class MainBackground { this.inlineMenuFieldQualificationService = new InlineMenuFieldQualificationService(); + this.phishingDataService = new PhishingDataService( + this.apiService, + this.taskSchedulerService, + this.globalStateProvider, + this.logService, + this.platformUtilsService, + ); + PhishingDetectionService.initialize( this.accountService, - this.auditService, this.billingAccountProfileStateService, this.configService, - this.eventCollectionService, this.logService, - this.storageService, - this.taskSchedulerService, + this.phishingDataService, + messageListener, ); this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService); @@ -1743,7 +1756,7 @@ export default class MainBackground { } await this.mainContextMenuHandler?.noAccess(); await this.systemService.clearPendingClipboard(); - await this.processReloadService.startProcessReload(this.authService); + await this.processReloadService.startProcessReload(); } private async needsStorageReseed(userId: UserId): Promise { diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 9dc2bff65e5..de0d79a89db 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -257,7 +257,7 @@ export default class RuntimeBackground { this.lockedVaultPendingNotifications.push(msg.data); break; case "lockVault": - await this.main.vaultTimeoutService.lock(msg.userId); + await this.lockService.lock(msg.userId); break; case "lockAll": { @@ -265,6 +265,14 @@ export default class RuntimeBackground { this.messagingService.send("lockAllFinished", { requestId: msg.requestId }); } break; + case "lockUser": + { + await this.lockService.lock(msg.userId); + this.messagingService.send("lockUserFinished", { + requestId: msg.requestId, + }); + } + break; case "logout": await this.main.logout(msg.expired, msg.userId); break; diff --git a/apps/browser/src/billing/popup/settings/premium-v2.component.html b/apps/browser/src/billing/popup/settings/premium-v2.component.html index 4f87a0f6781..47d72751af3 100644 --- a/apps/browser/src/billing/popup/settings/premium-v2.component.html +++ b/apps/browser/src/billing/popup/settings/premium-v2.component.html @@ -6,7 +6,7 @@
-

{{ "premiumFeatures" | i18n }}

+

{{ "premiumFeatures" | i18n }}

diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html index 5cac567c5c3..7675add73d7 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html +++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.html @@ -9,7 +9,7 @@

{{ "phishingPageSummary" | i18n }}

- {{ phishingHost$ | async }} + {{ phishingHostname$ | async }} diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts index 6087042629a..2b91a28122c 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts +++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts @@ -4,9 +4,10 @@ import { CommonModule } from "@angular/common"; import { Component, inject } from "@angular/core"; // eslint-disable-next-line no-restricted-imports import { ActivatedRoute, RouterModule } from "@angular/router"; -import { map } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api"; import { AsyncActionsModule, ButtonModule, @@ -18,8 +19,12 @@ import { CalloutComponent, TypographyModule, } from "@bitwarden/components"; +import { MessageSender } from "@bitwarden/messaging"; -import { PhishingDetectionService } from "../services/phishing-detection.service"; +import { + PHISHING_DETECTION_CANCEL_COMMAND, + PHISHING_DETECTION_CONTINUE_COMMAND, +} from "../services/phishing-detection.service"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -44,14 +49,29 @@ import { PhishingDetectionService } from "../services/phishing-detection.service }) export class PhishingWarning { private activatedRoute = inject(ActivatedRoute); - protected phishingHost$ = this.activatedRoute.queryParamMap.pipe( - map((params) => params.get("phishingHost") || ""), + private messageSender = inject(MessageSender); + + private phishingUrl$ = this.activatedRoute.queryParamMap.pipe( + map((params) => params.get("phishingUrl") || ""), ); + protected phishingHostname$ = this.phishingUrl$.pipe(map((url) => new URL(url).hostname)); async closeTab() { - await PhishingDetectionService.requestClosePhishingWarningPage(); + const tabId = await this.getTabId(); + this.messageSender.send(PHISHING_DETECTION_CANCEL_COMMAND, { + tabId, + }); } async continueAnyway() { - await PhishingDetectionService.requestContinueToDangerousUrl(); + const url = await firstValueFrom(this.phishingUrl$); + const tabId = await this.getTabId(); + this.messageSender.send(PHISHING_DETECTION_CONTINUE_COMMAND, { + tabId, + url, + }); + } + + private async getTabId() { + return BrowserApi.getCurrentTab()?.then((tab) => tab.id); } } diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts index b29d97451b8..e79543605c2 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts +++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.stories.ts @@ -10,6 +10,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { AnonLayoutComponent, I18nMockService } from "@bitwarden/components"; +import { MessageSender } from "@bitwarden/messaging"; import { PhishingWarning } from "./phishing-warning.component"; import { ProtectedByComponent } from "./protected-by-component"; @@ -49,6 +50,13 @@ export default { provide: PlatformUtilsService, useClass: MockPlatformUtilsService, }, + { + provide: MessageSender, + useValue: { + // eslint-disable-next-line no-console + send: (...args: any[]) => console.debug("MessageSender called with:", args), + } as Partial, + }, { provide: I18nService, useFactory: () => @@ -79,7 +87,7 @@ export default { }).asObservable(), }, }, - mockActivatedRoute({ phishingHost: "malicious-example.com" }), + mockActivatedRoute({ phishingUrl: "http://malicious-example.com" }), ], }), ], @@ -95,14 +103,7 @@ export default { `, }), - argTypes: { - phishingHost: { - control: "text", - description: "The suspicious host that was blocked", - }, - }, args: { - phishingHost: "malicious-example.com", pageIcon: DeactivatedOrg, }, } satisfies Meta; @@ -110,26 +111,20 @@ export default { type Story = StoryObj; export const Default: Story = { - args: { - phishingHost: "malicious-example.com", - }, decorators: [ moduleMetadata({ - providers: [mockActivatedRoute({ phishingHost: "malicious-example.com" })], + providers: [mockActivatedRoute({ phishingUrl: "http://malicious-example.com" })], }), ], }; export const LongHostname: Story = { - args: { - phishingHost: "very-long-suspicious-phishing-domain-name-that-might-wrap.malicious-example.com", - }, decorators: [ moduleMetadata({ providers: [ mockActivatedRoute({ - phishingHost: - "very-long-suspicious-phishing-domain-name-that-might-wrap.malicious-example.com", + phishingUrl: + "http://verylongsuspiciousphishingdomainnamethatmightwrapmaliciousexample.com", }), ], }), diff --git a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html index d9f26bc9c90..6c55097ade3 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html +++ b/apps/browser/src/dirt/phishing-detection/pages/protected-by-component.html @@ -1 +1 @@ -{{ "protectedBy" | i18n: "Bitwarden Phishing Blocker" }} +{{ "protectedBy" | i18n: "Bitwarden phishing blocker" }} diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts new file mode 100644 index 00000000000..94f3e99f8be --- /dev/null +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.spec.ts @@ -0,0 +1,158 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + DefaultTaskSchedulerService, + TaskSchedulerService, +} from "@bitwarden/common/platform/scheduling"; +import { FakeGlobalStateProvider } from "@bitwarden/common/spec"; +import { LogService } from "@bitwarden/logging"; + +import { PhishingDataService, PhishingData, PHISHING_DOMAINS_KEY } from "./phishing-data.service"; + +describe("PhishingDataService", () => { + let service: PhishingDataService; + let apiService: MockProxy; + let taskSchedulerService: TaskSchedulerService; + let logService: MockProxy; + let platformUtilsService: MockProxy; + const stateProvider: FakeGlobalStateProvider = new FakeGlobalStateProvider(); + + const setMockState = (state: PhishingData) => { + stateProvider.getFake(PHISHING_DOMAINS_KEY).stateSubject.next(state); + return state; + }; + + let fetchChecksumSpy: jest.SpyInstance; + let fetchDomainsSpy: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + apiService = mock(); + logService = mock(); + + platformUtilsService = mock(); + platformUtilsService.getApplicationVersion.mockResolvedValue("1.0.0"); + + taskSchedulerService = new DefaultTaskSchedulerService(logService); + + service = new PhishingDataService( + apiService, + taskSchedulerService, + stateProvider, + logService, + platformUtilsService, + ); + + fetchChecksumSpy = jest.spyOn(service as any, "fetchPhishingDomainsChecksum"); + fetchDomainsSpy = jest.spyOn(service as any, "fetchPhishingDomains"); + }); + + describe("isPhishingDomains", () => { + it("should detect a phishing domain", async () => { + setMockState({ + domains: ["phish.com", "badguy.net"], + timestamp: Date.now(), + checksum: "abc123", + applicationVersion: "1.0.0", + }); + const url = new URL("http://phish.com"); + const result = await service.isPhishingDomain(url); + expect(result).toBe(true); + }); + + it("should not detect a safe domain", async () => { + setMockState({ + domains: ["phish.com", "badguy.net"], + timestamp: Date.now(), + checksum: "abc123", + applicationVersion: "1.0.0", + }); + const url = new URL("http://safe.com"); + const result = await service.isPhishingDomain(url); + expect(result).toBe(false); + }); + + it("should match against root domain", async () => { + setMockState({ + domains: ["phish.com", "badguy.net"], + timestamp: Date.now(), + checksum: "abc123", + applicationVersion: "1.0.0", + }); + const url = new URL("http://phish.com/about"); + const result = await service.isPhishingDomain(url); + expect(result).toBe(true); + }); + + it("should not error on empty state", async () => { + setMockState(undefined as any); + const url = new URL("http://phish.com/about"); + const result = await service.isPhishingDomain(url); + expect(result).toBe(false); + }); + }); + + describe("getNextDomains", () => { + it("refetches all domains if applicationVersion has changed", async () => { + const prev: PhishingData = { + domains: ["a.com"], + timestamp: Date.now() - 60000, + checksum: "old", + applicationVersion: "1.0.0", + }; + fetchChecksumSpy.mockResolvedValue("new"); + fetchDomainsSpy.mockResolvedValue(["d.com", "e.com"]); + platformUtilsService.getApplicationVersion.mockResolvedValue("2.0.0"); + + const result = await service.getNextDomains(prev); + + expect(result!.domains).toEqual(["d.com", "e.com"]); + expect(result!.checksum).toBe("new"); + expect(result!.applicationVersion).toBe("2.0.0"); + }); + + it("only updates timestamp if checksum matches", async () => { + const prev: PhishingData = { + domains: ["a.com"], + timestamp: Date.now() - 60000, + checksum: "abc", + applicationVersion: "1.0.0", + }; + fetchChecksumSpy.mockResolvedValue("abc"); + const result = await service.getNextDomains(prev); + expect(result!.domains).toEqual(prev.domains); + expect(result!.checksum).toBe("abc"); + expect(result!.timestamp).not.toBe(prev.timestamp); + }); + + it("patches daily domains if cache is fresh", async () => { + const prev: PhishingData = { + domains: ["a.com"], + timestamp: Date.now() - 60000, + checksum: "old", + applicationVersion: "1.0.0", + }; + fetchChecksumSpy.mockResolvedValue("new"); + fetchDomainsSpy.mockResolvedValue(["b.com", "c.com"]); + const result = await service.getNextDomains(prev); + expect(result!.domains).toEqual(["a.com", "b.com", "c.com"]); + expect(result!.checksum).toBe("new"); + }); + + it("fetches all domains if cache is old", async () => { + const prev: PhishingData = { + domains: ["a.com"], + timestamp: Date.now() - 2 * 24 * 60 * 60 * 1000, + checksum: "old", + applicationVersion: "1.0.0", + }; + fetchChecksumSpy.mockResolvedValue("new"); + fetchDomainsSpy.mockResolvedValue(["d.com", "e.com"]); + const result = await service.getNextDomains(prev); + expect(result!.domains).toEqual(["d.com", "e.com"]); + expect(result!.checksum).toBe("new"); + }); + }); +}); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts new file mode 100644 index 00000000000..cb76a1cc354 --- /dev/null +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts @@ -0,0 +1,222 @@ +import { + catchError, + EMPTY, + first, + firstValueFrom, + map, + retry, + share, + startWith, + Subject, + switchMap, + tap, + timer, +} from "rxjs"; + +import { devFlagEnabled, devFlagValue } from "@bitwarden/browser/platform/flags"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { ScheduledTaskNames, TaskSchedulerService } from "@bitwarden/common/platform/scheduling"; +import { LogService } from "@bitwarden/logging"; +import { GlobalStateProvider, KeyDefinition, PHISHING_DETECTION_DISK } from "@bitwarden/state"; + +export type PhishingData = { + domains: string[]; + timestamp: number; + checksum: string; + + /** + * We store the application version to refetch the entire dataset on a new client release. + * This counteracts daily appends updates not removing inactive or false positive domains. + */ + applicationVersion: string; +}; + +export const PHISHING_DOMAINS_KEY = new KeyDefinition( + PHISHING_DETECTION_DISK, + "phishingDomains", + { + deserializer: (value: PhishingData) => + value ?? { domains: [], timestamp: 0, checksum: "", applicationVersion: "" }, + }, +); + +/** Coordinates fetching, caching, and patching of known phishing domains */ +export class PhishingDataService { + private static readonly RemotePhishingDatabaseUrl = + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/master/phishing-domains-ACTIVE.txt"; + private static readonly RemotePhishingDatabaseChecksumUrl = + "https://raw.githubusercontent.com/Phishing-Database/checksums/refs/heads/master/phishing-domains-ACTIVE.txt.md5"; + private static readonly RemotePhishingDatabaseTodayUrl = + "https://raw.githubusercontent.com/Phishing-Database/Phishing.Database/refs/heads/master/phishing-domains-NEW-today.txt"; + + private _testDomains = this.getTestDomains(); + private _cachedState = this.globalStateProvider.get(PHISHING_DOMAINS_KEY); + private _domains$ = this._cachedState.state$.pipe( + map( + (state) => + new Set( + (state?.domains?.filter((line) => line.trim().length > 0) ?? []).concat( + this._testDomains, + ), + ), + ), + ); + + // How often are new domains added to the remote? + readonly UPDATE_INTERVAL_DURATION = 24 * 60 * 60 * 1000; // 24 hours + + private _triggerUpdate$ = new Subject(); + update$ = this._triggerUpdate$.pipe( + startWith(undefined), // Always emit once + tap(() => this.logService.info(`[PhishingDataService] Update triggered...`)), + switchMap(() => + this._cachedState.state$.pipe( + first(), // Only take the first value to avoid an infinite loop when updating the cache below + switchMap(async (cachedState) => { + const next = await this.getNextDomains(cachedState); + if (next) { + await this._cachedState.update(() => next); + this.logService.info(`[PhishingDataService] cache updated`); + } + }), + retry({ + count: 3, + delay: (err, count) => { + this.logService.error( + `[PhishingDataService] Unable to update domains. Attempt ${count}.`, + err, + ); + return timer(5 * 60 * 1000); // 5 minutes + }, + resetOnSuccess: true, + }), + catchError( + ( + err: unknown /** Eslint actually crashed if you remove this type: https://github.com/cartant/eslint-plugin-rxjs/issues/122 */, + ) => { + this.logService.error( + "[PhishingDataService] Retries unsuccessful. Unable to update domains.", + err, + ); + return EMPTY; + }, + ), + ), + ), + share(), + ); + + constructor( + private apiService: ApiService, + private taskSchedulerService: TaskSchedulerService, + private globalStateProvider: GlobalStateProvider, + private logService: LogService, + private platformUtilsService: PlatformUtilsService, + ) { + this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.phishingDomainUpdate, () => { + this._triggerUpdate$.next(); + }); + this.taskSchedulerService.setInterval( + ScheduledTaskNames.phishingDomainUpdate, + this.UPDATE_INTERVAL_DURATION, + ); + } + + /** + * Checks if the given URL is a known phishing domain + * + * @param url The URL to check + * @returns True if the URL is a known phishing domain, false otherwise + */ + async isPhishingDomain(url: URL): Promise { + const domains = await firstValueFrom(this._domains$); + const result = domains.has(url.hostname); + if (result) { + return true; + } + return false; + } + + async getNextDomains(prev: PhishingData | null): Promise { + prev = prev ?? { domains: [], timestamp: 0, checksum: "", applicationVersion: "" }; + const timestamp = Date.now(); + const prevAge = timestamp - prev.timestamp; + this.logService.info(`[PhishingDataService] Cache age: ${prevAge}`); + + const applicationVersion = await this.platformUtilsService.getApplicationVersion(); + + // If checksum matches, return existing data with new timestamp & version + const remoteChecksum = await this.fetchPhishingDomainsChecksum(); + if (remoteChecksum && prev.checksum === remoteChecksum) { + this.logService.info( + `[PhishingDataService] Remote checksum matches local checksum, updating timestamp only.`, + ); + return { ...prev, timestamp, applicationVersion }; + } + // Checksum is different, data needs to be updated. + + // Approach 1: Fetch only new domains and append + const isOneDayOldMax = prevAge <= this.UPDATE_INTERVAL_DURATION; + if (isOneDayOldMax && applicationVersion === prev.applicationVersion) { + const dailyDomains: string[] = await this.fetchPhishingDomains( + PhishingDataService.RemotePhishingDatabaseTodayUrl, + ); + this.logService.info( + `[PhishingDataService] ${dailyDomains.length} new phishing domains added`, + ); + return { + domains: prev.domains.concat(dailyDomains), + checksum: remoteChecksum, + timestamp, + applicationVersion, + }; + } + + // Approach 2: Fetch all domains + const domains = await this.fetchPhishingDomains(PhishingDataService.RemotePhishingDatabaseUrl); + return { + domains, + timestamp, + checksum: remoteChecksum, + applicationVersion, + }; + } + + private async fetchPhishingDomainsChecksum() { + const response = await this.apiService.nativeFetch( + new Request(PhishingDataService.RemotePhishingDatabaseChecksumUrl), + ); + if (!response.ok) { + throw new Error(`[PhishingDataService] Failed to fetch checksum: ${response.status}`); + } + return response.text(); + } + + private async fetchPhishingDomains(url: string) { + const response = await this.apiService.nativeFetch(new Request(url)); + + if (!response.ok) { + throw new Error(`[PhishingDataService] Failed to fetch domains: ${response.status}`); + } + + return response.text().then((text) => text.split("\n")); + } + + private getTestDomains() { + const flag = devFlagEnabled("testPhishingUrls"); + if (!flag) { + return []; + } + + const domains = devFlagValue("testPhishingUrls") as unknown[]; + if (domains && domains instanceof Array) { + this.logService.debug( + "[PhishingDetectionService] Dev flag enabled for testing phishing detection. Adding test phishing domains:", + domains, + ); + return domains as string[]; + } + return []; + } +} diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts index d6aca6abea0..e33b4b1b4f1 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.spec.ts @@ -1,127 +1,86 @@ -import { of } from "rxjs"; +import { mock, MockProxy } from "jest-mock-extended"; +import { Observable, of } from "rxjs"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling/task-scheduler.service"; +import { MessageListener } from "@bitwarden/messaging"; +import { PhishingDataService } from "./phishing-data.service"; import { PhishingDetectionService } from "./phishing-detection.service"; describe("PhishingDetectionService", () => { let accountService: AccountService; - let auditService: AuditService; let billingAccountProfileStateService: BillingAccountProfileStateService; let configService: ConfigService; - let eventCollectionService: EventCollectionService; let logService: LogService; - let storageService: AbstractStorageService; - let taskSchedulerService: TaskSchedulerService; + let phishingDataService: MockProxy; + let messageListener: MockProxy; beforeEach(() => { accountService = { getAccount$: jest.fn(() => of(null)) } as any; - auditService = { getKnownPhishingDomains: jest.fn() } as any; billingAccountProfileStateService = {} as any; configService = { getFeatureFlag$: jest.fn(() => of(false)) } as any; - eventCollectionService = {} as any; logService = { info: jest.fn(), debug: jest.fn(), warning: jest.fn(), error: jest.fn() } as any; - storageService = { get: jest.fn(), save: jest.fn() } as any; - taskSchedulerService = { registerTaskHandler: jest.fn(), setInterval: jest.fn() } as any; + phishingDataService = mock(); + messageListener = mock({ + messages$(_commandDefinition) { + return new Observable(); + }, + }); }); it("should initialize without errors", () => { expect(() => { PhishingDetectionService.initialize( accountService, - auditService, billingAccountProfileStateService, configService, - eventCollectionService, logService, - storageService, - taskSchedulerService, + phishingDataService, + messageListener, ); }).not.toThrow(); }); - it("should enable phishing detection for premium account", (done) => { - const premiumAccount = { id: "user1" }; - accountService = { activeAccount$: of(premiumAccount) } as any; - configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; - billingAccountProfileStateService = { - hasPremiumFromAnySource$: jest.fn(() => of(true)), - } as any; + // TODO + // it("should enable phishing detection for premium account", (done) => { + // const premiumAccount = { id: "user1" }; + // accountService = { activeAccount$: of(premiumAccount) } as any; + // configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; + // billingAccountProfileStateService = { + // hasPremiumFromAnySource$: jest.fn(() => of(true)), + // } as any; - // Patch _setup to call done - const setupSpy = jest - .spyOn(PhishingDetectionService as any, "_setup") - .mockImplementation(async () => { - expect(setupSpy).toHaveBeenCalled(); - done(); - }); + // // Run the initialization + // PhishingDetectionService.initialize( + // accountService, + // billingAccountProfileStateService, + // configService, + // logService, + // phishingDataService, + // messageListener, + // ); + // }); - // Run the initialization - PhishingDetectionService.initialize( - accountService, - auditService, - billingAccountProfileStateService, - configService, - eventCollectionService, - logService, - storageService, - taskSchedulerService, - ); - }); + // TODO + // it("should not enable phishing detection for non-premium account", (done) => { + // const nonPremiumAccount = { id: "user2" }; + // accountService = { activeAccount$: of(nonPremiumAccount) } as any; + // configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; + // billingAccountProfileStateService = { + // hasPremiumFromAnySource$: jest.fn(() => of(false)), + // } as any; - it("should not enable phishing detection for non-premium account", (done) => { - const nonPremiumAccount = { id: "user2" }; - accountService = { activeAccount$: of(nonPremiumAccount) } as any; - configService = { getFeatureFlag$: jest.fn(() => of(true)) } as any; - billingAccountProfileStateService = { - hasPremiumFromAnySource$: jest.fn(() => of(false)), - } as any; - - // Patch _setup to fail if called - // [FIXME] This test needs to check if the setupSpy fails or is called - // Refactor initialize in PhishingDetectionService to return a Promise or Observable that resolves/completes when initialization is done - // So that spy setups can be properly verified after initialization - // const setupSpy = jest - // .spyOn(PhishingDetectionService as any, "_setup") - // .mockImplementation(async () => { - // throw new Error("Should not call _setup"); - // }); - - // Patch _cleanup to call done - const cleanupSpy = jest - .spyOn(PhishingDetectionService as any, "_cleanup") - .mockImplementation(() => { - expect(cleanupSpy).toHaveBeenCalled(); - done(); - }); - - // Run the initialization - PhishingDetectionService.initialize( - accountService, - auditService, - billingAccountProfileStateService, - configService, - eventCollectionService, - logService, - storageService, - taskSchedulerService, - ); - }); - - it("should detect phishing domains", () => { - PhishingDetectionService["_knownPhishingDomains"].add("phishing.com"); - const url = new URL("https://phishing.com"); - expect(PhishingDetectionService.isPhishingDomain(url)).toBe(true); - const safeUrl = new URL("https://safe.com"); - expect(PhishingDetectionService.isPhishingDomain(safeUrl)).toBe(false); - }); - - // Add more tests for other methods as needed + // // Run the initialization + // PhishingDetectionService.initialize( + // accountService, + // billingAccountProfileStateService, + // configService, + // logService, + // phishingDataService, + // messageListener, + // ); + // }); }); diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts index 179431b155c..4917e740be8 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.service.ts @@ -1,709 +1,193 @@ import { combineLatest, concatMap, - delay, + distinctUntilChanged, EMPTY, + filter, map, + merge, + of, Subject, - Subscription, switchMap, + tap, } from "rxjs"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; -import { devFlagEnabled, devFlagValue } from "@bitwarden/common/platform/misc/flags"; -import { ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; -import { TaskSchedulerService } from "@bitwarden/common/platform/scheduling/task-scheduler.service"; +import { CommandDefinition, MessageListener } from "@bitwarden/messaging"; import { BrowserApi } from "../../../platform/browser/browser-api"; -import { - CaughtPhishingDomain, - isPhishingDetectionMessage, - PhishingDetectionMessage, - PhishingDetectionNavigationEvent, - PhishingDetectionTabId, -} from "./phishing-detection.types"; +import { PhishingDataService } from "./phishing-data.service"; + +type PhishingDetectionNavigationEvent = { + tabId: number; + changeInfo: chrome.tabs.OnUpdatedInfo; + tab: chrome.tabs.Tab; +}; + +/** + * Sends a message to the phishing detection service to continue to the caught url + */ +export const PHISHING_DETECTION_CONTINUE_COMMAND = new CommandDefinition<{ + tabId: number; + url: string; +}>("phishing-detection-continue"); + +/** + * Sends a message to the phishing detection service to close the warning page + */ +export const PHISHING_DETECTION_CANCEL_COMMAND = new CommandDefinition<{ + tabId: number; +}>("phishing-detection-cancel"); export class PhishingDetectionService { - private static readonly _UPDATE_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds - private static readonly _RETRY_INTERVAL = 5 * 60 * 1000; // 5 minutes - private static readonly _MAX_RETRIES = 3; - private static readonly _STORAGE_KEY = "phishing_domains_cache"; - private static _auditService: AuditService; - private static _logService: LogService; - private static _storageService: AbstractStorageService; - private static _taskSchedulerService: TaskSchedulerService; - private static _updateCacheSubscription: Subscription | null = null; - private static _retrySubscription: Subscription | null = null; - private static _navigationEventsSubject = new Subject(); - private static _navigationEvents: Subscription | null = null; - private static _knownPhishingDomains = new Set(); - private static _caughtTabs: Map = new Map(); - private static _isInitialized = false; - private static _isUpdating = false; - private static _retryCount = 0; - private static _lastUpdateTime: number = 0; + private static _tabUpdated$ = new Subject(); + private static _ignoredHostnames = new Set(); + private static _didInit = false; static initialize( accountService: AccountService, - auditService: AuditService, billingAccountProfileStateService: BillingAccountProfileStateService, configService: ConfigService, - eventCollectionService: EventCollectionService, logService: LogService, - storageService: AbstractStorageService, - taskSchedulerService: TaskSchedulerService, - ): void { - this._auditService = auditService; - this._logService = logService; - this._storageService = storageService; - this._taskSchedulerService = taskSchedulerService; + phishingDataService: PhishingDataService, + messageListener: MessageListener, + ) { + if (this._didInit) { + logService.debug("[PhishingDetectionService] Initialize already called. Aborting."); + return; + } - logService.info("[PhishingDetectionService] Initialize called. Checking prerequisites..."); + logService.debug("[PhishingDetectionService] Initialize called. Checking prerequisites..."); - combineLatest([ + BrowserApi.addListener(chrome.tabs.onUpdated, this._handleTabUpdated.bind(this)); + + const onContinueCommand$ = messageListener.messages$(PHISHING_DETECTION_CONTINUE_COMMAND).pipe( + tap((message) => + logService.debug(`[PhishingDetectionService] user selected continue for ${message.url}`), + ), + concatMap(async (message) => { + const url = new URL(message.url); + this._ignoredHostnames.add(url.hostname); + await BrowserApi.navigateTabToUrl(message.tabId, url); + }), + ); + + const onTabUpdated$ = this._tabUpdated$.pipe( + filter( + (navEvent) => + navEvent.changeInfo.status === "complete" && + !!navEvent.tab.url && + !this._isExtensionPage(navEvent.tab.url), + ), + map(({ tab, tabId }) => { + const url = new URL(tab.url!); + return { tabId, url, ignored: this._ignoredHostnames.has(url.hostname) }; + }), + distinctUntilChanged( + (prev, curr) => + prev.url.toString() === curr.url.toString() && + prev.tabId === curr.tabId && + prev.ignored === curr.ignored, + ), + tap((event) => logService.debug(`[PhishingDetectionService] processing event:`, event)), + concatMap(async ({ tabId, url, ignored }) => { + if (ignored) { + // The next time this host is visited, block again + this._ignoredHostnames.delete(url.hostname); + return; + } + const isPhishing = await phishingDataService.isPhishingDomain(url); + if (!isPhishing) { + return; + } + + const phishingWarningPage = new URL( + BrowserApi.getRuntimeURL("popup/index.html#/security/phishing-warning") + + `?phishingUrl=${url.toString()}`, + ); + await BrowserApi.navigateTabToUrl(tabId, phishingWarningPage); + }), + ); + + const onCancelCommand$ = messageListener + .messages$(PHISHING_DETECTION_CANCEL_COMMAND) + .pipe(switchMap((message) => BrowserApi.closeTab(message.tabId))); + + const activeAccountHasAccess$ = combineLatest([ accountService.activeAccount$, configService.getFeatureFlag$(FeatureFlag.PhishingDetection), - ]) + ]).pipe( + switchMap(([account, featureEnabled]) => { + if (!account) { + logService.debug("[PhishingDetectionService] No active account."); + return of(false); + } + return billingAccountProfileStateService + .hasPremiumFromAnySource$(account.id) + .pipe(map((hasPremium) => hasPremium && featureEnabled)); + }), + ); + + const initSub = activeAccountHasAccess$ .pipe( - switchMap(([account, featureEnabled]) => { - if (!account) { - logService.info("[PhishingDetectionService] No active account."); - this._cleanup(); - return EMPTY; - } - return billingAccountProfileStateService - .hasPremiumFromAnySource$(account.id) - .pipe(map((hasPremium) => ({ hasPremium, featureEnabled }))); - }), - concatMap(async ({ hasPremium, featureEnabled }) => { - if (!hasPremium || !featureEnabled) { - logService.info( + distinctUntilChanged(), + switchMap((activeUserHasAccess) => { + if (!activeUserHasAccess) { + logService.debug( "[PhishingDetectionService] User does not have access to phishing detection service.", ); - this._cleanup(); + return EMPTY; } else { - logService.info("[PhishingDetectionService] Enabling phishing detection service"); - await this._setup(); + logService.debug("[PhishingDetectionService] Enabling phishing detection service"); + return merge( + phishingDataService.update$, + onContinueCommand$, + onTabUpdated$, + onCancelCommand$, + ); } }), ) .subscribe(); - } - /** - * Checks if the given URL is a known phishing domain - * - * @param url The URL to check - * @returns True if the URL is a known phishing domain, false otherwise - */ - static isPhishingDomain(url: URL): boolean { - const result = this._knownPhishingDomains.has(url.hostname); - if (result) { - this._logService.debug("[PhishingDetectionService] Caught phishing domain:", url.hostname); - return true; - } - return false; - } + this._didInit = true; + return () => { + initSub.unsubscribe(); + this._didInit = false; - /** - * Sends a message to the phishing detection service to close the warning page - */ - static async requestClosePhishingWarningPage() { - await chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Close }); - } - - /** - * Sends a message to the phishing detection service to continue to the caught url - */ - static async requestContinueToDangerousUrl() { - await chrome.runtime.sendMessage({ command: PhishingDetectionMessage.Continue }); - } - - /** - * Continues to the dangerous URL if the user has requested it - * - * @param tabId The ID of the tab to continue to the dangerous URL - */ - static async _continueToDangerousUrl(tabId: PhishingDetectionTabId): Promise { - const caughtTab = this._caughtTabs.get(tabId); - if (caughtTab) { - this._logService.info( - "[PhishingDetectionService] Continuing to known phishing domain: ", - caughtTab, - caughtTab.url.href, + // Manually type cast to satisfy the listener signature due to the mixture + // of static and instance methods in this class. To be fixed when refactoring + // this class to be instance-based while providing a singleton instance in usage + BrowserApi.removeListener( + chrome.tabs.onUpdated, + PhishingDetectionService._handleTabUpdated as (...args: readonly unknown[]) => unknown, ); - await BrowserApi.navigateTabToUrl(tabId, caughtTab.url); - } else { - this._logService.warning("[PhishingDetectionService] No caught domain to continue to"); - } + }; } - /** - * Initializes the phishing detection service, setting up listeners and registering tasks - */ - private static async _setup(): Promise { - if (this._isInitialized) { - this._logService.info("[PhishingDetectionService] Already initialized, skipping setup."); - return; - } - - this._isInitialized = true; - this._setupListeners(); - - // Register the update task - this._taskSchedulerService.registerTaskHandler( - ScheduledTaskNames.phishingDomainUpdate, - async () => { - try { - await this._fetchKnownPhishingDomains(); - } catch (error) { - this._logService.error( - "[PhishingDetectionService] Failed to update phishing domains in task handler:", - error, - ); - } - }, - ); - - // Initial load of cached domains - await this._loadCachedDomains(); - - // Set up periodic updates every 24 hours - this._setupPeriodicUpdates(); - this._logService.debug("[PhishingDetectionService] Phishing detection feature is initialized."); - } - - /** - * Sets up listeners for messages from the web page and web navigation events - */ - private static _setupListeners(): void { - // Setup listeners from web page/content script - BrowserApi.addListener(chrome.runtime.onMessage, this._handleExtensionMessage.bind(this)); - BrowserApi.addListener(chrome.tabs.onReplaced, this._handleReplacementEvent.bind(this)); - BrowserApi.addListener(chrome.tabs.onUpdated, this._handleNavigationEvent.bind(this)); - - // When a navigation event occurs, check if a replace event for the same tabId exists, - // and call the replace handler before handling navigation. - this._navigationEvents = this._navigationEventsSubject - .pipe( - delay(100), // Delay slightly to allow replace events to be caught - ) - .subscribe(({ tabId, changeInfo, tab }) => { - void this._processNavigation(tabId, changeInfo, tab); - }); - } - - /** - * Handles messages from the phishing warning page - * - * @returns true if the message was handled, false otherwise - */ - private static _handleExtensionMessage( - message: unknown, - sender: chrome.runtime.MessageSender, - ): boolean { - if (!isPhishingDetectionMessage(message)) { - return false; - } - const isValidSender = sender && sender.tab && sender.tab.id; - const senderTabId = isValidSender ? sender?.tab?.id : null; - - // Only process messages from tab navigation - if (senderTabId == null) { - return false; - } - - // Handle Dangerous Continue to Phishing Domain - if (message.command === PhishingDetectionMessage.Continue) { - this._logService.debug( - "[PhishingDetectionService] User requested continue to phishing domain on tab: ", - senderTabId, - ); - - this._setCaughtTabContinue(senderTabId); - void this._continueToDangerousUrl(senderTabId); - return true; - } - - // Handle Close Phishing Warning Page - if (message.command === PhishingDetectionMessage.Close) { - this._logService.debug( - "[PhishingDetectionService] User requested to close phishing warning page on tab: ", - senderTabId, - ); - - void BrowserApi.closeTab(senderTabId); - this._removeCaughtTab(senderTabId); - return true; - } - - return false; - } - - /** - * Filter out navigation events that are to warning pages or not complete, check for phishing domains, - * then handle the navigation appropriately. - */ - private static async _processNavigation( - tabId: number, - changeInfo: chrome.tabs.OnUpdatedInfo, - tab: chrome.tabs.Tab, - ): Promise { - if (changeInfo.status !== "complete" || !tab.url) { - // Not a complete navigation or no URL to check - return; - } - // Check if navigating to a warning page to ignore - const isWarningPage = this._isWarningPage(tabId, tab.url); - if (isWarningPage) { - this._logService.debug( - `[PhishingDetectionService] Ignoring navigation to warning page for tab ${tabId}: ${tab.url}`, - ); - return; - } - - // Check if tab is navigating to a phishing url and handle navigation - this._checkTabForPhishing(tabId, new URL(tab.url)); - await this._handleTabNavigation(tabId); - } - - private static _handleNavigationEvent( + private static _handleTabUpdated( tabId: number, changeInfo: chrome.tabs.OnUpdatedInfo, tab: chrome.tabs.Tab, ): boolean { - this._navigationEventsSubject.next({ tabId, changeInfo, tab }); + this._tabUpdated$.next({ tabId, changeInfo, tab }); // Return value for supporting BrowserApi event listener signature return true; } - /** - * Handles a replace event in Safari when redirecting to a warning page - * - * @returns true if the replacement was handled, false otherwise - */ - private static _handleReplacementEvent(newTabId: number, originalTabId: number): boolean { - if (this._caughtTabs.has(originalTabId)) { - this._logService.debug( - `[PhishingDetectionService] Handling original tab ${originalTabId} changing to new tab ${newTabId}`, - ); - - // Handle replacement - const originalCaughtTab = this._caughtTabs.get(originalTabId); - if (originalCaughtTab) { - this._caughtTabs.set(newTabId, originalCaughtTab); - this._caughtTabs.delete(originalTabId); - } else { - this._logService.debug( - `[PhishingDetectionService] Original caught tab not found, ignoring replacement.`, - ); - } - return true; - } - return false; - } - - /** - * Adds a tab to the caught tabs map with the requested continue status set to false - * - * @param tabId The ID of the tab that was caught - * @param url The URL of the tab that was caught - * @param redirectedTo The URL that the tab was redirected to - */ - private static _addCaughtTab(tabId: PhishingDetectionTabId, url: URL) { - const redirectedTo = this._createWarningPageUrl(url); - const newTab = { url, warningPageUrl: redirectedTo, requestedContinue: false }; - - this._caughtTabs.set(tabId, newTab); - this._logService.debug("[PhishingDetectionService] Tracking new tab:", tabId, newTab); - } - - /** - * Removes a tab from the caught tabs map - * - * @param tabId The ID of the tab to remove - */ - private static _removeCaughtTab(tabId: PhishingDetectionTabId) { - this._logService.debug("[PhishingDetectionService] Removing tab from tracking: ", tabId); - this._caughtTabs.delete(tabId); - } - - /** - * Sets the requested continue status for a caught tab - * - * @param tabId The ID of the tab to set the continue status for - */ - private static _setCaughtTabContinue(tabId: PhishingDetectionTabId) { - const caughtTab = this._caughtTabs.get(tabId); - if (caughtTab) { - this._caughtTabs.set(tabId, { - url: caughtTab.url, - warningPageUrl: caughtTab.warningPageUrl, - requestedContinue: true, - }); - } - } - - /** - * Checks if the tab should continue to a dangerous domain - * - * @param tabId Tab to check if a domain was caught - * @returns True if the user requested to continue to the phishing domain - */ - private static _continueToCaughtDomain(tabId: PhishingDetectionTabId) { - const caughtDomain = this._caughtTabs.get(tabId); - const hasRequestedContinue = caughtDomain?.requestedContinue; - return caughtDomain && hasRequestedContinue; - } - - /** - * Checks if the tab is going to a phishing domain and updates the caught tabs map - * - * @param tabId Tab to check for phishing domain - * @param url URL of the tab to check - */ - private static _checkTabForPhishing(tabId: PhishingDetectionTabId, url: URL) { - // Check if the tab already being tracked - const caughtTab = this._caughtTabs.get(tabId); - - const isPhishing = this.isPhishingDomain(url); - this._logService.debug( - `[PhishingDetectionService] Checking for phishing url. Result: ${isPhishing} on ${url}`, - ); - - // Add a new caught tab - if (!caughtTab && isPhishing) { - this._addCaughtTab(tabId, url); - } - - // The tab was caught before but has an updated url - if (caughtTab && caughtTab.url.href !== url.href) { - if (isPhishing) { - this._logService.debug( - "[PhishingDetectionService] Caught tab going to a new phishing domain:", - caughtTab.url, - ); - // The tab can be treated as a new tab, clear the old one and reset - this._removeCaughtTab(tabId); - this._addCaughtTab(tabId, url); - } else { - this._logService.debug( - "[PhishingDetectionService] Caught tab navigating away from a phishing domain", - ); - // The tab is safe - this._removeCaughtTab(tabId); - } - } - } - - /** - * Handles a phishing tab for redirection to a warning page if the user has not requested to continue - * - * @param tabId Tab to handle - * @param url URL of the tab - */ - private static async _handleTabNavigation(tabId: PhishingDetectionTabId) { - const caughtTab = this._caughtTabs.get(tabId); - - if (caughtTab && !this._continueToCaughtDomain(tabId)) { - await this._redirectToWarningPage(tabId); - } - } - - private static _isWarningPage(tabId: number, url: string): boolean { - const caughtTab = this._caughtTabs.get(tabId); - return !!caughtTab && caughtTab.warningPageUrl.href === url; - } - - /** - * Constructs the phishing warning page URL with the caught URL as a query parameter - * - * @param caughtUrl The URL that was caught as phishing - * @returns The complete URL to the phishing warning page - */ - private static _createWarningPageUrl(caughtUrl: URL) { - const phishingWarningPage = BrowserApi.getRuntimeURL( - "popup/index.html#/security/phishing-warning", - ); - const pageWithViewData = `${phishingWarningPage}?phishingHost=${caughtUrl.hostname}`; - this._logService.debug( - "[PhishingDetectionService] Created phishing warning page url:", - pageWithViewData, - ); - return new URL(pageWithViewData); - } - - /** - * Redirects the tab to the phishing warning page - * - * @param tabId The ID of the tab to redirect - */ - private static async _redirectToWarningPage(tabId: number) { - const tabToRedirect = this._caughtTabs.get(tabId); - - if (tabToRedirect) { - this._logService.info("[PhishingDetectionService] Redirecting to warning page"); - await BrowserApi.navigateTabToUrl(tabId, tabToRedirect.warningPageUrl); - } else { - this._logService.warning("[PhishingDetectionService] No caught tab found for redirection"); - } - } - - /** - * Sets up periodic updates for phishing domains - */ - private static _setupPeriodicUpdates() { - // Clean up any existing subscriptions - if (this._updateCacheSubscription) { - this._updateCacheSubscription.unsubscribe(); - } - if (this._retrySubscription) { - this._retrySubscription.unsubscribe(); - } - - this._updateCacheSubscription = this._taskSchedulerService.setInterval( - ScheduledTaskNames.phishingDomainUpdate, - this._UPDATE_INTERVAL, - ); - } - - /** - * Schedules a retry for updating phishing domains if the update fails - */ - private static _scheduleRetry() { - // If we've exceeded max retries, stop retrying - if (this._retryCount >= this._MAX_RETRIES) { - this._logService.warning( - `[PhishingDetectionService] Max retries (${this._MAX_RETRIES}) reached for phishing domain update. Will try again in ${this._UPDATE_INTERVAL / (1000 * 60 * 60)} hours.`, - ); - this._retryCount = 0; - if (this._retrySubscription) { - this._retrySubscription.unsubscribe(); - this._retrySubscription = null; - } - return; - } - - // Clean up existing retry subscription if any - if (this._retrySubscription) { - this._retrySubscription.unsubscribe(); - } - - // Increment retry count - this._retryCount++; - - // Schedule a retry in 5 minutes - this._retrySubscription = this._taskSchedulerService.setInterval( - ScheduledTaskNames.phishingDomainUpdate, - this._RETRY_INTERVAL, - ); - - this._logService.info( - `[PhishingDetectionService] Scheduled retry ${this._retryCount}/${this._MAX_RETRIES} for phishing domain update in ${this._RETRY_INTERVAL / (1000 * 60)} minutes`, - ); - } - - /** - * Handles adding test phishing URLs from dev flags for testing purposes - */ - private static _handleTestUrls() { - if (devFlagEnabled("testPhishingUrls")) { - const testPhishingUrls = devFlagValue("testPhishingUrls"); - this._logService.debug( - "[PhishingDetectionService] Dev flag enabled for testing phishing detection. Adding test phishing domains:", - testPhishingUrls, - ); - if (testPhishingUrls && testPhishingUrls instanceof Array) { - testPhishingUrls.forEach((domain) => { - if (domain && typeof domain === "string") { - this._knownPhishingDomains.add(domain); - } - }); - } - } - } - - /** - * Loads cached phishing domains from storage - * If no cache exists or it is expired, fetches the latest domains - */ - private static async _loadCachedDomains() { - try { - const cachedData = await this._storageService.get<{ domains: string[]; timestamp: number }>( - this._STORAGE_KEY, - ); - if (cachedData) { - this._logService.info("[PhishingDetectionService] Phishing cachedData exists"); - const phishingDomains = cachedData.domains || []; - - this._setKnownPhishingDomains(phishingDomains); - this._handleTestUrls(); - } - - // If cache is empty or expired, trigger an immediate update - if ( - this._knownPhishingDomains.size === 0 || - Date.now() - this._lastUpdateTime >= this._UPDATE_INTERVAL - ) { - await this._fetchKnownPhishingDomains(); - } - } catch (error) { - this._logService.error( - "[PhishingDetectionService] Failed to load cached phishing domains:", - error, - ); - this._handleTestUrls(); - } - } - - /** - * Fetches the latest known phishing domains from the audit service - * Updates the cache and handles retries if necessary - */ - static async _fetchKnownPhishingDomains(): Promise { - let domains: string[] = []; - - // Prevent concurrent updates - if (this._isUpdating) { - this._logService.warning( - "[PhishingDetectionService] Update already in progress, skipping...", - ); - return; - } - - try { - this._logService.info("[PhishingDetectionService] Starting phishing domains update..."); - this._isUpdating = true; - domains = await this._auditService.getKnownPhishingDomains(); - this._setKnownPhishingDomains(domains); - - await this._saveDomains(); - - this._resetRetry(); - this._isUpdating = false; - - this._logService.info("[PhishingDetectionService] Successfully fetched domains"); - } catch (error) { - this._logService.error( - "[PhishingDetectionService] Failed to fetch known phishing domains.", - error, - ); - - this._scheduleRetry(); - this._isUpdating = false; - - throw error; - } - } - - /** - * Saves the known phishing domains to storage - * Caches the updated domains and updates the last update time - */ - private static async _saveDomains() { - try { - // Cache the updated domains - await this._storageService.save(this._STORAGE_KEY, { - domains: Array.from(this._knownPhishingDomains), - timestamp: this._lastUpdateTime, - }); - this._logService.info( - `[PhishingDetectionService] Updated phishing domains cache with ${this._knownPhishingDomains.size} domains`, - ); - } catch (error) { - this._logService.error( - "[PhishingDetectionService] Failed to save known phishing domains.", - error, - ); - this._scheduleRetry(); - throw error; - } - } - - /** - * Resets the retry count and clears the retry subscription - */ - private static _resetRetry(): void { - this._logService.info( - `[PhishingDetectionService] Resetting retry count and clearing retry subscription.`, - ); - // Reset retry count and clear retry subscription on success - this._retryCount = 0; - if (this._retrySubscription) { - this._retrySubscription.unsubscribe(); - this._retrySubscription = null; - } - } - - /** - * Adds phishing domains to the known phishing domains set - * Clears old domains to prevent memory leaks - * - * @param domains Array of phishing domains to add - */ - private static _setKnownPhishingDomains(domains: string[]): void { - this._logService.debug( - `[PhishingDetectionService] Tracking ${domains.length} phishing domains`, - ); - - // Clear old domains to prevent memory leaks - this._knownPhishingDomains.clear(); - - domains.forEach((domain: string) => { - if (domain) { - this._knownPhishingDomains.add(domain); - } - }); - this._lastUpdateTime = Date.now(); - } - - /** - * Cleans up the phishing detection service - * Unsubscribes from all subscriptions and clears caches - */ - private static _cleanup() { - if (this._updateCacheSubscription) { - this._updateCacheSubscription.unsubscribe(); - this._updateCacheSubscription = null; - } - if (this._retrySubscription) { - this._retrySubscription.unsubscribe(); - this._retrySubscription = null; - } - if (this._navigationEvents) { - this._navigationEvents.unsubscribe(); - this._navigationEvents = null; - } - this._knownPhishingDomains.clear(); - this._caughtTabs.clear(); - this._lastUpdateTime = 0; - this._isUpdating = false; - this._isInitialized = false; - this._retryCount = 0; - - // Manually type cast to satisfy the listener signature due to the mixture - // of static and instance methods in this class. To be fixed when refactoring - // this class to be instance-based while providing a singleton instance in usage - BrowserApi.removeListener( - chrome.runtime.onMessage, - PhishingDetectionService._handleExtensionMessage as (...args: readonly unknown[]) => unknown, - ); - BrowserApi.removeListener( - chrome.tabs.onReplaced, - PhishingDetectionService._handleReplacementEvent as (...args: readonly unknown[]) => unknown, - ); - BrowserApi.removeListener( - chrome.tabs.onUpdated, - PhishingDetectionService._handleNavigationEvent as (...args: readonly unknown[]) => unknown, + private static _isExtensionPage(url: string): boolean { + // Check against all common extension protocols + return ( + url.startsWith("chrome-extension://") || + url.startsWith("moz-extension://") || + url.startsWith("safari-extension://") || + url.startsWith("safari-web-extension://") ); } } diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.types.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-detection.types.ts deleted file mode 100644 index 21793616241..00000000000 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-detection.types.ts +++ /dev/null @@ -1,35 +0,0 @@ -export const PhishingDetectionMessage = Object.freeze({ - Close: "phishing-detection-close", - Continue: "phishing-detection-continue", -} as const); - -export type PhishingDetectionMessageTypes = - (typeof PhishingDetectionMessage)[keyof typeof PhishingDetectionMessage]; - -export function isPhishingDetectionMessage( - input: unknown, -): input is { command: PhishingDetectionMessageTypes } { - if (!!input && typeof input === "object" && "command" in input) { - const command = (input as Record)["command"]; - if (typeof command === "string") { - return Object.values(PhishingDetectionMessage).includes( - command as PhishingDetectionMessageTypes, - ); - } - } - return false; -} - -export type PhishingDetectionTabId = number; - -export type CaughtPhishingDomain = { - url: URL; - warningPageUrl: URL; - requestedContinue: boolean; -}; - -export type PhishingDetectionNavigationEvent = { - tabId: number; - changeInfo: chrome.tabs.OnUpdatedInfo; - tab: chrome.tabs.Tab; -}; diff --git a/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts index 4081ab03359..8bad50bfae9 100644 --- a/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts +++ b/apps/browser/src/key-management/vault-timeout/foreground-vault-timeout.service.ts @@ -2,15 +2,10 @@ // @ts-strict-ignore import { VaultTimeoutService as BaseVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout/abstractions/vault-timeout.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { UserId } from "@bitwarden/common/types/guid"; export class ForegroundVaultTimeoutService implements BaseVaultTimeoutService { constructor(protected messagingService: MessagingService) {} // should only ever run in background async checkVaultTimeout(): Promise {} - - async lock(userId?: UserId): Promise { - this.messagingService.send("lockVault", { userId }); - } } diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index e218abd2d10..d44a3d2a2e7 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.10.1", + "version": "2025.11.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 6f4fc905f44..b6381201c7d 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.10.1", + "version": "2025.11.0", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 8a3dbafc5ce..76ec18f496f 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -5,6 +5,7 @@ import { Observable } from "rxjs"; import { BrowserClientVendors } from "@bitwarden/common/autofill/constants"; import { BrowserClientVendor } from "@bitwarden/common/autofill/types"; import { DeviceType } from "@bitwarden/common/enums"; +import { LogService } from "@bitwarden/logging"; import { isBrowserSafariApi } from "@bitwarden/platform"; import { TabMessage } from "../../types/tab-messages"; @@ -32,6 +33,53 @@ export class BrowserApi { return BrowserApi.manifestVersion === expectedVersion; } + /** + * Helper method that attempts to distinguish whether a message sender is internal to the extension or not. + * + * Currently this is done through source origin matching, and frameId checking (only top-level frames are internal). + * @param sender a message sender + * @param logger an optional logger to log validation results + * @returns whether or not the sender appears to be internal to the extension + */ + static senderIsInternal( + sender: chrome.runtime.MessageSender | undefined, + logger?: LogService, + ): boolean { + if (!sender?.origin) { + logger?.warning("[BrowserApi] Message sender has no origin"); + return false; + } + const extensionUrl = + (typeof chrome !== "undefined" && chrome.runtime?.getURL("")) || + (typeof browser !== "undefined" && browser.runtime?.getURL("")) || + ""; + + if (!extensionUrl) { + logger?.warning("[BrowserApi] Unable to determine extension URL"); + return false; + } + + // Normalize both URLs by removing trailing slashes + const normalizedOrigin = sender.origin.replace(/\/$/, ""); + const normalizedExtensionUrl = extensionUrl.replace(/\/$/, ""); + + if (!normalizedOrigin.startsWith(normalizedExtensionUrl)) { + logger?.warning( + `[BrowserApi] Message sender origin (${normalizedOrigin}) does not match extension URL (${normalizedExtensionUrl})`, + ); + return false; + } + + // We only send messages from the top-level frame, but frameId is only set if tab is set, which for popups it is not. + if ("frameId" in sender && sender.frameId !== 0) { + logger?.warning("[BrowserApi] Message sender is not from the top-level frame"); + return false; + } + + logger?.info("[BrowserApi] Message sender appears to be internal"); + return true; + } + /** * Gets all open browser windows, including their tabs. * diff --git a/apps/browser/src/platform/browser/browser-popup-utils.spec.ts b/apps/browser/src/platform/browser/browser-popup-utils.spec.ts index e4165348c6e..6e2175e3a79 100644 --- a/apps/browser/src/platform/browser/browser-popup-utils.spec.ts +++ b/apps/browser/src/platform/browser/browser-popup-utils.spec.ts @@ -140,6 +140,11 @@ describe("BrowserPopupUtils", () => { describe("openPopout", () => { beforeEach(() => { + jest.spyOn(BrowserApi, "getPlatformInfo").mockResolvedValueOnce({ + os: "linux", + arch: "x86-64", + nacl_arch: "x86-64", + }); jest.spyOn(BrowserApi, "getWindow").mockResolvedValueOnce({ id: 1, left: 100, @@ -150,6 +155,8 @@ describe("BrowserPopupUtils", () => { width: 380, }); jest.spyOn(BrowserApi, "createWindow").mockImplementation(); + jest.spyOn(BrowserApi, "updateWindowProperties").mockImplementation(); + jest.spyOn(BrowserApi, "getPlatformInfo").mockImplementation(); }); it("creates a window with the default window options", async () => { @@ -267,6 +274,63 @@ describe("BrowserPopupUtils", () => { url: `chrome-extension://id/${url}?uilocation=popout&singleActionPopout=123`, }); }); + + it("exits fullscreen and focuses popout window if the current window is fullscreen and platform is mac", async () => { + const url = "popup/index.html"; + jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false); + jest.spyOn(BrowserApi, "getPlatformInfo").mockReset().mockResolvedValueOnce({ + os: "mac", + arch: "x86-64", + nacl_arch: "x86-64", + }); + jest.spyOn(BrowserApi, "getWindow").mockReset().mockResolvedValueOnce({ + id: 1, + left: 100, + top: 100, + focused: false, + alwaysOnTop: false, + incognito: false, + width: 380, + state: "fullscreen", + }); + jest + .spyOn(BrowserApi, "createWindow") + .mockResolvedValueOnce({ id: 2 } as chrome.windows.Window); + + await BrowserPopupUtils.openPopout(url, { senderWindowId: 1 }); + expect(BrowserApi.updateWindowProperties).toHaveBeenCalledWith(1, { + state: "maximized", + }); + expect(BrowserApi.updateWindowProperties).toHaveBeenCalledWith(2, { + focused: true, + }); + }); + + it("doesnt exit fullscreen if the platform is not mac", async () => { + const url = "popup/index.html"; + jest.spyOn(BrowserPopupUtils as any, "isSingleActionPopoutOpen").mockResolvedValueOnce(false); + jest.spyOn(BrowserApi, "getPlatformInfo").mockReset().mockResolvedValueOnce({ + os: "win", + arch: "x86-64", + nacl_arch: "x86-64", + }); + jest.spyOn(BrowserApi, "getWindow").mockResolvedValueOnce({ + id: 1, + left: 100, + top: 100, + focused: false, + alwaysOnTop: false, + incognito: false, + width: 380, + state: "fullscreen", + }); + + await BrowserPopupUtils.openPopout(url); + + expect(BrowserApi.updateWindowProperties).not.toHaveBeenCalledWith(1, { + state: "maximized", + }); + }); }); describe("openCurrentPagePopout", () => { diff --git a/apps/browser/src/platform/browser/browser-popup-utils.ts b/apps/browser/src/platform/browser/browser-popup-utils.ts index cd55f6361a0..8343799d0eb 100644 --- a/apps/browser/src/platform/browser/browser-popup-utils.ts +++ b/apps/browser/src/platform/browser/browser-popup-utils.ts @@ -168,8 +168,29 @@ export default class BrowserPopupUtils { ) { return; } + const platform = await BrowserApi.getPlatformInfo(); + const isMacOS = platform.os === "mac"; + const isFullscreen = senderWindow.state === "fullscreen"; + const isFullscreenAndMacOS = isFullscreen && isMacOS; + //macOS specific handling for improved UX when sender in fullscreen aka green button; + if (isFullscreenAndMacOS) { + await BrowserApi.updateWindowProperties(senderWindow.id, { + state: "maximized", + }); - return await BrowserApi.createWindow(popoutWindowOptions); + //wait for macOS animation to finish + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + const newWindow = await BrowserApi.createWindow(popoutWindowOptions); + + if (isFullscreenAndMacOS) { + await BrowserApi.updateWindowProperties(newWindow.id, { + focused: true, + }); + } + + return newWindow; } /** diff --git a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts index ca79a6d9d14..c6ffe1a6414 100644 --- a/apps/browser/src/platform/popup/layout/popup-layout.stories.ts +++ b/apps/browser/src/platform/popup/layout/popup-layout.stories.ts @@ -29,11 +29,9 @@ import { SearchModule, SectionComponent, ScrollLayoutDirective, - SkeletonComponent, - SkeletonTextComponent, - SkeletonGroupComponent, } from "@bitwarden/components"; +import { VaultLoadingSkeletonComponent } from "../../../vault/popup/components/vault-loading-skeleton/vault-loading-skeleton.component"; import { PopupRouterCacheService } from "../view-cache/popup-router-cache.service"; import { PopupFooterComponent } from "./popup-footer.component"; @@ -366,9 +364,7 @@ export default { SectionComponent, IconButtonModule, BadgeModule, - SkeletonComponent, - SkeletonTextComponent, - SkeletonGroupComponent, + VaultLoadingSkeletonComponent, ], providers: [ { @@ -634,21 +630,9 @@ export const SkeletonLoading: Story = { template: /* HTML */ ` - + -
-
Loading...
-
- - @for (num of data; track $index) { - - - - - - } -
-
+
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 b53ef6e97eb..a9184a9dd54 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.html +++ b/apps/browser/src/platform/popup/layout/popup-page.component.html @@ -1,7 +1,7 @@
-
@@ -37,9 +39,9 @@
- +
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.ts b/apps/browser/src/platform/popup/layout/popup-page.component.ts index db5ea641691..4eed322bdbd 100644 --- a/apps/browser/src/platform/popup/layout/popup-page.component.ts +++ b/apps/browser/src/platform/popup/layout/popup-page.component.ts @@ -1,11 +1,16 @@ import { CommonModule } from "@angular/common"; -import { booleanAttribute, Component, inject, Input, signal } from "@angular/core"; +import { + booleanAttribute, + ChangeDetectionStrategy, + Component, + inject, + input, + signal, +} from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ScrollLayoutHostDirective } from "@bitwarden/components"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "popup-page", templateUrl: "popup-page.component.html", @@ -13,28 +18,23 @@ import { ScrollLayoutHostDirective } from "@bitwarden/components"; class: "tw-h-full tw-flex tw-flex-col tw-overflow-y-hidden", }, imports: [CommonModule, ScrollLayoutHostDirective], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PopupPageComponent { protected i18nService = inject(I18nService); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() loading = false; + readonly loading = input(false); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input({ transform: booleanAttribute }) - disablePadding = false; + readonly disablePadding = input(false, { transform: booleanAttribute }); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - protected scrolled = signal(false); + /** Hides any overflow within the page content */ + readonly hideOverflow = input(false, { transform: booleanAttribute }); + + protected readonly scrolled = signal(false); isScrolled = this.scrolled.asReadonly(); /** Accessible loading label for the spinner. Defaults to "loading" */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() loadingText?: string = this.i18nService.t("loading"); + readonly loadingText = input(this.i18nService.t("loading")); handleScroll(event: Event) { this.scrolled.set((event.currentTarget as HTMLElement).scrollTop !== 0); diff --git a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html index 0a52518b250..bce2b5033ae 100644 --- a/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html +++ b/apps/browser/src/platform/popup/layout/popup-tab-navigation.component.html @@ -8,7 +8,7 @@
  • } -

    +

    {{ "currentWebsite" | i18n }}

    @@ -61,7 +61,7 @@ bitLink linkType="secondary" (click)="close()" - class="tw-mt-2 tw-font-bold tw-text-sm tw-justify-center tw-text-center" + class="tw-mt-2 tw-font-medium tw-text-sm tw-justify-center tw-text-center" > {{ "doNotAutofill" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts index e8f00cd7b8d..52ab4adcc0c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts @@ -199,7 +199,7 @@ describe("AutofillConfirmationDialogComponent", () => { it("shows the 'view all' button when savedUrls > 1 and hides it after click", () => { const findViewAll = () => fixture.nativeElement.querySelector( - "button.tw-text-sm.tw-font-bold.tw-cursor-pointer", + "button.tw-text-sm.tw-font-medium.tw-cursor-pointer", ) as HTMLButtonElement | null; let btn = findViewAll(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index 66bbafb7c31..5927da6c3d2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -144,6 +144,15 @@ describe("ItemMoreOptionsComponent", () => { } describe("doAutofill", () => { + it("calls the passwordService to passwordRepromptCheck", async () => { + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + mockConfirmDialogResult(AutofillConfirmationDialogResult.AutofilledOnly); + + await component.doAutofill(); + + expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); + }); + it("calls the autofill service to autofill without showing the confirmation dialog when the feature flag is disabled or search text is not present", async () => { autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); @@ -160,15 +169,6 @@ describe("ItemMoreOptionsComponent", () => { expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); }); - it("calls the passwordService to passwordRepromptCheck", async () => { - autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); - mockConfirmDialogResult(AutofillConfirmationDialogResult.AutofilledOnly); - - await component.doAutofill(); - - expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); - }); - it("does nothing if the user fails master password reprompt", async () => { baseCipher.reprompt = 2; // Master Password reprompt enabled autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); @@ -181,13 +181,33 @@ describe("ItemMoreOptionsComponent", () => { expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); }); + it("does not show the exact match dialog when the default match strategy is Exact and autofill confirmation is not to be shown", async () => { + // autofill confirmation dialog is not shown when either the feature flag is disabled or search text is not present + uriMatchStrategy$.next(UriMatchStrategy.Exact); + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + describe("autofill confirmation dialog", () => { beforeEach(() => { + // autofill confirmation dialog is shown when feature flag is enabled and search text is present featureFlag$.next(true); hasSearchText$.next(true); + uriMatchStrategy$.next(UriMatchStrategy.Domain); passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true); }); + it("calls the passwordService to passwordRepromptCheck", async () => { + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + mockConfirmDialogResult(AutofillConfirmationDialogResult.AutofilledOnly); + + await component.doAutofill(); + + expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); + }); + it("opens the autofill confirmation dialog with filtered saved URLs when the feature flag is enabled and search text is present", async () => { autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); const openSpy = mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); @@ -243,47 +263,130 @@ describe("ItemMoreOptionsComponent", () => { }); describe("URI match strategy handling", () => { - it("shows the exact match dialog when the uri match strategy is Exact", async () => { - uriMatchStrategy$.next(UriMatchStrategy.Exact); - autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); + describe("when the default URI match strategy is Exact", () => { + beforeEach(() => { + uriMatchStrategy$.next(UriMatchStrategy.Exact); + }); - await component.doAutofill(); + it("calls the passwordService to passwordRepromptCheck", async () => { + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + mockConfirmDialogResult(AutofillConfirmationDialogResult.AutofilledOnly); - expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); - expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( - expect.objectContaining({ - title: expect.objectContaining({ key: "cannotAutofill" }), - content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), - type: "info", - }), - ); - expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); - expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + await component.doAutofill(); + + expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); + }); + + it("shows the exact match dialog", async () => { + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( + expect.objectContaining({ + title: expect.objectContaining({ key: "cannotAutofill" }), + content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), + type: "info", + }), + ); + expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); + expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + }); }); - it("shows the exact match dialog and not the password reprompt dialog when the uri match strategy is Exact and the item has master password reprompt enabled", async () => { - uriMatchStrategy$.next(UriMatchStrategy.Exact); + describe("when the default URI match strategy is not Exact", () => { + beforeEach(() => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + uriMatchStrategy$.next(UriMatchStrategy.Domain); + }); + it("does not show the exact match dialog", async () => { + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [ + { uri: "https://one.example.com", match: UriMatchStrategy.Exact }, + { uri: "https://page.example.com", match: UriMatchStrategy.Domain }, + ], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + + it("shows the exact match dialog when the cipher has a single uri with a match strategy of Exact", async () => { + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [{ uri: "https://one.example.com", match: UriMatchStrategy.Exact }], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( + expect.objectContaining({ + title: expect.objectContaining({ key: "cannotAutofill" }), + content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), + type: "info", + }), + ); + expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); + expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + }); + }); + + it("does not show the exact match dialog when the cipher has no uris", async () => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [], + }, + })); + autofillSvc.currentAutofillTab$.next({ url: "https://no-match.example.com" }); await component.doAutofill(); - expect(dialogService.openSimpleDialog).toHaveBeenCalledTimes(1); - expect(dialogService.openSimpleDialog).toHaveBeenCalledWith( - expect.objectContaining({ - title: expect.objectContaining({ key: "cannotAutofill" }), - content: expect.objectContaining({ key: "cannotAutofillExactMatch" }), - type: "info", - }), - ); - expect(autofillSvc.doAutofill).not.toHaveBeenCalled(); - expect(passwordRepromptService.passwordRepromptCheck).not.toHaveBeenCalled(); - expect(autofillSvc.doAutofillAndSave).not.toHaveBeenCalled(); + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + }); + + it("does not show the exact match dialog when the cipher has a uri with a match strategy of Exact and a uri with a match strategy of Domain", async () => { + mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); + cipherService.getFullCipherView.mockImplementation(async (c) => ({ + ...baseCipher, + ...c, + login: { + ...baseCipher.login, + uris: [ + { uri: "https://one.example.com", match: UriMatchStrategy.Exact }, + { uri: "https://page.example.com", match: UriMatchStrategy.Domain }, + ], + }, + })); + + autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); + + await component.doAutofill(); + + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); }); }); it("hides the 'Fill and Save' button when showAutofillConfirmation$ is true", async () => { - // Enable both feature flag and search text → makes showAutofillConfirmation$ true - fixture.detectChanges(); await fixture.whenStable(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 6bc9d3aa207..1316a0d32b8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -202,8 +202,21 @@ export class ItemMoreOptionsComponent { async doAutofill() { const cipher = await this.cipherService.getFullCipherView(this.cipher); + if (!(await this.passwordRepromptService.passwordRepromptCheck(this.cipher))) { + return; + } + + const uris = cipher.login?.uris ?? []; + const cipherHasAllExactMatchLoginUris = + uris.length > 0 && uris.every((u) => u.uri && u.match === UriMatchStrategy.Exact); + + const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$); const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$); - if (uriMatchStrategy === UriMatchStrategy.Exact) { + + if ( + showAutofillConfirmation && + (cipherHasAllExactMatchLoginUris || uriMatchStrategy === UriMatchStrategy.Exact) + ) { await this.dialogService.openSimpleDialog({ title: { key: "cannotAutofill" }, content: { key: "cannotAutofillExactMatch" }, @@ -214,12 +227,6 @@ export class ItemMoreOptionsComponent { return; } - if (!(await this.passwordRepromptService.passwordRepromptCheck(this.cipher))) { - return; - } - - const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$); - if (!showAutofillConfirmation) { await this.vaultPopupAutofillService.doAutofill(cipher, true, true); return; @@ -284,7 +291,7 @@ export class ItemMoreOptionsComponent { this.toastService.showToast({ variant: "success", message: this.i18nService.t( - this.cipher.favorite ? "itemAddedToFavorites" : "itemRemovedFromFavorites", + cipher.favorite ? "itemAddedToFavorites" : "itemRemovedFromFavorites", ), }); } 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 fad5615764c..3dac158b8e1 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 @@ -84,7 +84,7 @@ -

    +

    {{ group.subHeaderKey | i18n }}

    diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 30074777e83..1dea91c0b9f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -330,6 +330,7 @@ export class ViewV2Component { const tab = await BrowserApi.getTab(senderTabId); await sendExtensionMessage("bgHandleReprompt", { tab, + cipherId: cipher.id, success: repromptSuccess, }); diff --git a/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.spec.ts b/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.spec.ts index 9a00bacd6b0..bf63cf1f668 100644 --- a/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.spec.ts +++ b/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.spec.ts @@ -2,25 +2,69 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { DialogService } from "@bitwarden/components"; + import { BrowserPremiumUpgradePromptService } from "./browser-premium-upgrade-prompt.service"; describe("BrowserPremiumUpgradePromptService", () => { let service: BrowserPremiumUpgradePromptService; let router: MockProxy; + let configService: MockProxy; + let dialogService: MockProxy; beforeEach(async () => { router = mock(); + configService = mock(); + dialogService = mock(); + await TestBed.configureTestingModule({ - providers: [BrowserPremiumUpgradePromptService, { provide: Router, useValue: router }], + providers: [ + BrowserPremiumUpgradePromptService, + { provide: Router, useValue: router }, + { provide: ConfigService, useValue: configService }, + { provide: DialogService, useValue: dialogService }, + ], }).compileComponents(); service = TestBed.inject(BrowserPremiumUpgradePromptService); }); describe("promptForPremium", () => { - it("navigates to the premium update screen", async () => { + let openSpy: jest.SpyInstance; + + beforeEach(() => { + openSpy = jest.spyOn(PremiumUpgradeDialogComponent, "open").mockImplementation(); + }); + + afterEach(() => { + openSpy.mockRestore(); + }); + + it("opens the new premium upgrade dialog when feature flag is enabled", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + await service.promptForPremium(); + + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); + expect(openSpy).toHaveBeenCalledWith(dialogService); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it("navigates to the premium update screen when feature flag is disabled", async () => { + configService.getFeatureFlag.mockResolvedValue(false); + + await service.promptForPremium(); + + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); expect(router.navigate).toHaveBeenCalledWith(["/premium"]); + expect(openSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.ts b/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.ts index 2909e3b3bd6..53f7ffd5f5a 100644 --- a/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.ts +++ b/apps/browser/src/vault/popup/services/browser-premium-upgrade-prompt.service.ts @@ -1,18 +1,32 @@ import { inject } from "@angular/core"; import { Router } from "@angular/router"; +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { DialogService } from "@bitwarden/components"; /** * This class handles the premium upgrade process for the browser extension. */ export class BrowserPremiumUpgradePromptService implements PremiumUpgradePromptService { private router = inject(Router); + private configService = inject(ConfigService); + private dialogService = inject(DialogService); async promptForPremium() { - /** - * Navigate to the premium update screen. - */ - await this.router.navigate(["/premium"]); + const showNewDialog = await this.configService.getFeatureFlag( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); + + if (showNewDialog) { + PremiumUpgradeDialogComponent.open(this.dialogService); + } else { + /** + * Navigate to the premium update screen. + */ + await this.router.navigate(["/premium"]); + } } } diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html index c9598c76db0..b58316a8d64 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.html +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -41,7 +41,7 @@ {{ "showAnimations" | i18n }} -

    {{ "vaultCustomization" | i18n }}

    +

    {{ "vaultCustomization" | i18n }}

    diff --git a/apps/browser/tailwind.config.js b/apps/browser/tailwind.config.js index 1ad56562bb3..134001bbf13 100644 --- a/apps/browser/tailwind.config.js +++ b/apps/browser/tailwind.config.js @@ -10,6 +10,7 @@ config.content = [ "../../libs/vault/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts}", + "../../libs/pricing/src/**/*.{html,ts}", ]; module.exports = config; diff --git a/apps/cli/package.json b/apps/cli/package.json index 02627f80a27..26e1183004a 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/cli", "description": "A secure and free password manager for all of your devices.", - "version": "2025.10.1", + "version": "2025.11.0", "keywords": [ "bitwarden", "password", diff --git a/apps/cli/src/auth/commands/lock.command.ts b/apps/cli/src/auth/commands/lock.command.ts index f3b8018f40e..eef85980d58 100644 --- a/apps/cli/src/auth/commands/lock.command.ts +++ b/apps/cli/src/auth/commands/lock.command.ts @@ -1,16 +1,22 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; +import { firstValueFrom } from "rxjs"; + +import { LockService } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Response } from "../../models/response"; import { MessageResponse } from "../../models/response/message.response"; export class LockCommand { - constructor(private vaultTimeoutService: VaultTimeoutService) {} + constructor( + private lockService: LockService, + private accountService: AccountService, + ) {} async run() { - await this.vaultTimeoutService.lock(); - process.env.BW_SESSION = null; + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.lockService.lock(activeUserId); + process.env.BW_SESSION = undefined; const res = new MessageResponse("Your vault is locked.", null); return Response.success(res); } diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index a994ad3117c..93e711d748f 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map, switchMap } from "rxjs"; +import { filter, firstValueFrom, map, switchMap } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -448,7 +448,9 @@ export class GetCommand extends DownloadCommand { this.collectionService.encryptedCollections$(activeUserId).pipe(getById(id)), ); if (collection != null) { - const orgKeys = await firstValueFrom(this.keyService.activeUserOrgKeys$); + const orgKeys = await firstValueFrom( + this.keyService.orgKeys$(activeUserId).pipe(filter((orgKeys) => orgKeys != null)), + ); decCollection = await collection.decrypt( orgKeys[collection.organizationId as OrganizationId], this.encryptService, diff --git a/apps/cli/src/key-management/cli-process-reload.service.ts b/apps/cli/src/key-management/cli-process-reload.service.ts new file mode 100644 index 00000000000..243de7cae43 --- /dev/null +++ b/apps/cli/src/key-management/cli-process-reload.service.ts @@ -0,0 +1,10 @@ +import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; + +/** + * CLI implementation of ProcessReloadServiceAbstraction. + * This is NOOP since there is no effective way to process reload the CLI. + */ +export class CliProcessReloadService extends ProcessReloadServiceAbstraction { + async startProcessReload(): Promise {} + async cancelProcessReload(): Promise {} +} diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index d318a44c677..bd51cf4dd91 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -160,7 +160,10 @@ export class OssServeConfigurator { this.serviceContainer.cipherService, this.serviceContainer.accountService, ); - this.lockCommand = new LockCommand(this.serviceContainer.vaultTimeoutService); + this.lockCommand = new LockCommand( + serviceContainer.lockService, + serviceContainer.accountService, + ); this.unlockCommand = new UnlockCommand( this.serviceContainer.accountService, this.serviceContainer.masterPasswordService, diff --git a/apps/cli/src/platform/services/cli-system.service.ts b/apps/cli/src/platform/services/cli-system.service.ts new file mode 100644 index 00000000000..5f647a0f88c --- /dev/null +++ b/apps/cli/src/platform/services/cli-system.service.ts @@ -0,0 +1,10 @@ +import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; + +/** + * CLI implementation of SystemService. + * The implementation is NOOP since these functions are meant for GUI clients. + */ +export class CliSystemService extends SystemService { + async clearClipboard(clipboardValue: string, timeoutMs?: number): Promise {} + async clearPendingClipboard(): Promise {} +} diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index 41368269faf..a5f12b34035 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -250,7 +250,10 @@ export class Program extends BaseProgram { return; } - const command = new LockCommand(this.serviceContainer.vaultTimeoutService); + const command = new LockCommand( + this.serviceContainer.lockService, + this.serviceContainer.accountService, + ); const response = await command.run(); this.processResponse(response); }); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 3c4ee55361f..c9f1d11210b 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -20,6 +20,9 @@ import { SsoUrlService, AuthRequestApiServiceAbstraction, DefaultAuthRequestApiService, + DefaultLockService, + DefaultLogoutService, + LockService, } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -199,9 +202,11 @@ import { } from "@bitwarden/vault-export-core"; import { CliBiometricsService } from "../key-management/cli-biometrics-service"; +import { CliProcessReloadService } from "../key-management/cli-process-reload.service"; import { flagEnabled } from "../platform/flags"; import { CliPlatformUtilsService } from "../platform/services/cli-platform-utils.service"; import { CliSdkLoadService } from "../platform/services/cli-sdk-load.service"; +import { CliSystemService } from "../platform/services/cli-system.service"; import { ConsoleLogService } from "../platform/services/console-log.service"; import { I18nService } from "../platform/services/i18n.service"; import { LowdbStorageService } from "../platform/services/lowdb-storage.service"; @@ -318,6 +323,7 @@ export class ServiceContainer { securityStateService: SecurityStateService; masterPasswordUnlockService: MasterPasswordUnlockService; cipherArchiveService: CipherArchiveService; + lockService: LockService; constructor() { let p = null; @@ -778,9 +784,6 @@ export class ServiceContainer { this.folderApiService = new FolderApiService(this.folderService, this.apiService); - const lockedCallback = async (userId: UserId) => - await this.keyService.clearStoredUserKey(userId); - this.userVerificationApiService = new UserVerificationApiService(this.apiService); this.userVerificationService = new UserVerificationService( @@ -796,25 +799,35 @@ export class ServiceContainer { ); const biometricService = new CliBiometricsService(); + const logoutService = new DefaultLogoutService(this.messagingService); + const processReloadService = new CliProcessReloadService(); + const systemService = new CliSystemService(); + this.lockService = new DefaultLockService( + this.accountService, + biometricService, + this.vaultTimeoutSettingsService, + logoutService, + this.messagingService, + this.searchService, + this.folderService, + this.masterPasswordService, + this.stateEventRunnerService, + this.cipherService, + this.authService, + systemService, + processReloadService, + this.logService, + this.keyService, + ); this.vaultTimeoutService = new DefaultVaultTimeoutService( this.accountService, - this.masterPasswordService, - this.cipherService, - this.folderService, - this.collectionService, this.platformUtilsService, - this.messagingService, - this.searchService, - this.stateService, - this.tokenService, this.authService, this.vaultTimeoutSettingsService, - this.stateEventRunnerService, this.taskSchedulerService, this.logService, - biometricService, - lockedCallback, + this.lockService, undefined, ); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 03a205e9c48..5602c593942 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -92,18 +92,18 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const cipherView = CipherExport.toView(req); - const isCipherTypeRestricted = - await this.cliRestrictedItemTypesService.isCipherRestricted(cipherView); - - if (isCipherTypeRestricted) { - return Response.error("Creating this item type is restricted by organizational policy."); - } - - const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const cipherView = CipherExport.toView(req); + const isCipherTypeRestricted = + await this.cliRestrictedItemTypesService.isCipherRestricted(cipherView); + + if (isCipherTypeRestricted) { + return Response.error("Creating this item type is restricted by organizational policy."); + } + + const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); const newCipher = await this.cipherService.createWithServer(cipher); const decCipher = await this.cipherService.decrypt(newCipher, activeUserId); const res = new CipherResponse(decCipher); diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index cf635f39df3..98018a3d056 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -444,8 +444,10 @@ dependencies = [ name = "bitwarden_chromium_import_helper" version = "0.0.0" dependencies = [ + "aes-gcm", "anyhow", "base64", + "chacha20poly1305", "chromium_importer", "clap", "embed-resource", @@ -454,7 +456,6 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", - "verifysign", "windows 0.61.1", ] @@ -607,7 +608,6 @@ dependencies = [ "async-trait", "base64", "cbc", - "chacha20poly1305", "dirs", "hex", "oo7", @@ -620,7 +620,7 @@ dependencies = [ "sha1", "tokio", "tracing", - "tracing-subscriber", + "verifysign", "windows 0.61.1", ] @@ -900,19 +900,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "der" version = "0.7.10" @@ -1533,12 +1520,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.3" @@ -1554,7 +1535,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown", ] [[package]] @@ -1719,7 +1700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown", ] [[package]] @@ -1891,7 +1872,6 @@ version = "0.0.0" dependencies = [ "desktop_core", "futures", - "oslog", "serde", "serde_json", "tokio", @@ -2379,17 +2359,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "oslog" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" -dependencies = [ - "cc", - "dashmap", - "log", -] - [[package]] name = "p256" version = "0.13.2" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 7a835b81c1b..dffa8d72594 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -20,6 +20,7 @@ publish = false [workspace.dependencies] aes = "=0.8.4" +aes-gcm = "=0.10.3" anyhow = "=1.0.94" arboard = { version = "=3.6.0", default-features = false } ashpd = "=0.11.0" @@ -41,13 +42,11 @@ interprocess = "=2.2.1" keytar = "=0.1.6" libc = "=0.2.172" linux-keyutils = "=0.2.4" -log = "=0.4.25" memsec = "=0.7.0" napi = "=2.16.17" napi-build = "=2.2.0" napi-derive = "=2.16.13" oo7 = "=0.4.3" -oslog = "=0.2.0" pin-project = "=1.1.10" pkcs8 = "=0.10.2" rand = "=0.9.1" @@ -60,7 +59,6 @@ security-framework-sys = "=2.15.0" serde = "=1.0.209" serde_json = "=1.0.127" sha2 = "=0.10.8" -simplelog = "=0.12.2" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.35.0" @@ -86,10 +84,13 @@ zbus_polkit = "=5.0.0" zeroizing-alloc = "=0.1.0" [workspace.lints.clippy] +disallowed-macros = "deny" + # Dis-allow println and eprintln, which are typically used in debugging. # Use `tracing` and `tracing-subscriber` crates for observability needs. print_stderr = "deny" print_stdout = "deny" + string_slice = "warn" unused_async = "deny" unwrap_used = "deny" diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml index dc5358b0c73..576a7d048fc 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml @@ -8,23 +8,14 @@ publish.workspace = true [dependencies] [target.'cfg(target_os = "windows")'.dependencies] +aes-gcm = { workspace = true } +chacha20poly1305 = { workspace = true } chromium_importer = { path = "../chromium_importer" } clap = { version = "=4.5.40", features = ["derive"] } scopeguard = { workspace = true } sysinfo = { workspace = true } windows = { workspace = true, features = [ - "Wdk_System_SystemServices", - "Win32_Security_Cryptography", - "Win32_Security", - "Win32_Storage_FileSystem", - "Win32_System_IO", - "Win32_System_Memory", "Win32_System_Pipes", - "Win32_System_ProcessStatus", - "Win32_System_Services", - "Win32_System_Threading", - "Win32_UI_Shell", - "Win32_UI_WindowsAndMessaging", ] } anyhow = { workspace = true } base64 = { workspace = true } diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows.rs deleted file mode 100644 index 9abc8c95a1f..00000000000 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows.rs +++ /dev/null @@ -1,482 +0,0 @@ -mod windows_binary { - use anyhow::{anyhow, Result}; - use base64::{engine::general_purpose, Engine as _}; - use clap::Parser; - use scopeguard::defer; - use std::{ - ffi::OsString, - os::windows::{ffi::OsStringExt as _, io::AsRawHandle}, - path::{Path, PathBuf}, - ptr, - time::Duration, - }; - use sysinfo::System; - use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::windows::named_pipe::{ClientOptions, NamedPipeClient}, - time, - }; - use tracing::{debug, error, level_filters::LevelFilter}; - use tracing_subscriber::{ - fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, Layer as _, - }; - use windows::{ - core::BOOL, - Wdk::System::SystemServices::SE_DEBUG_PRIVILEGE, - Win32::{ - Foundation::{ - CloseHandle, LocalFree, ERROR_PIPE_BUSY, HANDLE, HLOCAL, NTSTATUS, STATUS_SUCCESS, - }, - Security::{ - self, - Cryptography::{CryptUnprotectData, CRYPTPROTECT_UI_FORBIDDEN, CRYPT_INTEGER_BLOB}, - DuplicateToken, ImpersonateLoggedOnUser, RevertToSelf, TOKEN_DUPLICATE, - TOKEN_QUERY, - }, - System::{ - Pipes::GetNamedPipeServerProcessId, - Threading::{ - OpenProcess, OpenProcessToken, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, - PROCESS_QUERY_LIMITED_INFORMATION, - }, - }, - UI::Shell::IsUserAnAdmin, - }, - }; - - use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME}; - - #[derive(Parser)] - #[command(name = "bitwarden_chromium_import_helper")] - #[command(about = "Admin tool for ABE service management")] - struct Args { - /// Base64 encoded encrypted data to process - #[arg(long, help = "Base64 encoded encrypted data string")] - encrypted: String, - } - - // Enable this to log to a file. The way this executable is used, it's not easy to debug and the stdout gets lost. - // This is intended for development time only. All the logging is wrapped in `dbg_log!`` macro that compiles to - // no-op when logging is disabled. This is needed to avoid any sensitive data being logged in production. Normally - // all the logging code is present in the release build and could be enabled via RUST_LOG environment variable. - // We don't want that! - const ENABLE_DEVELOPER_LOGGING: bool = false; - const LOG_FILENAME: &str = "c:\\path\\to\\log.txt"; // This is an example filename, replace it with you own - - // This should be enabled for production - const ENABLE_SERVER_SIGNATURE_VALIDATION: bool = true; - - // List of SYSTEM process names to try to impersonate - const SYSTEM_PROCESS_NAMES: [&str; 2] = ["services.exe", "winlogon.exe"]; - - // Macro wrapper around debug! that compiles to no-op when ENABLE_DEVELOPER_LOGGING is false - macro_rules! dbg_log { - ($($arg:tt)*) => { - if ENABLE_DEVELOPER_LOGGING { - debug!($($arg)*); - } - }; - } - - async fn open_pipe_client(pipe_name: &'static str) -> Result { - let max_attempts = 5; - for _ in 0..max_attempts { - match ClientOptions::new().open(pipe_name) { - Ok(client) => { - dbg_log!("Successfully connected to the pipe!"); - return Ok(client); - } - Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY.0 as i32) => { - dbg_log!("Pipe is busy, retrying in 50ms..."); - } - Err(e) => { - dbg_log!("Failed to connect to pipe: {}", &e); - return Err(e.into()); - } - } - - time::sleep(Duration::from_millis(50)).await; - } - - Err(anyhow!( - "Failed to connect to pipe after {} attempts", - max_attempts - )) - } - - async fn send_message_with_client( - client: &mut NamedPipeClient, - message: &str, - ) -> Result { - client.write_all(message.as_bytes()).await?; - - // Try to receive a response for this message - let mut buffer = vec![0u8; 64 * 1024]; - match client.read(&mut buffer).await { - Ok(0) => Err(anyhow!( - "Server closed the connection (0 bytes read) on message" - )), - Ok(bytes_received) => { - let response = String::from_utf8_lossy(&buffer[..bytes_received]); - Ok(response.to_string()) - } - Err(e) => Err(anyhow!("Failed to receive response for message: {}", e)), - } - } - - fn get_named_pipe_server_pid(client: &NamedPipeClient) -> Result { - let handle = HANDLE(client.as_raw_handle() as _); - let mut pid: u32 = 0; - unsafe { GetNamedPipeServerProcessId(handle, &mut pid) }?; - Ok(pid) - } - - fn resolve_process_executable_path(pid: u32) -> Result { - dbg_log!("Resolving process executable path for PID {}", pid); - - // Open the process handle - let hprocess = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?; - dbg_log!("Opened process handle for PID {}", pid); - - // Close when no longer needed - defer! { - dbg_log!("Closing process handle for PID {}", pid); - unsafe { - _ = CloseHandle(hprocess); - } - }; - - let mut exe_name = vec![0u16; 32 * 1024]; - let mut exe_name_length = exe_name.len() as u32; - unsafe { - QueryFullProcessImageNameW( - hprocess, - PROCESS_NAME_WIN32, - windows::core::PWSTR(exe_name.as_mut_ptr()), - &mut exe_name_length, - ) - }?; - dbg_log!( - "QueryFullProcessImageNameW returned {} bytes", - exe_name_length - ); - - exe_name.truncate(exe_name_length as usize); - Ok(PathBuf::from(OsString::from_wide(&exe_name))) - } - - async fn send_error_to_user(client: &mut NamedPipeClient, error_message: &str) { - _ = send_to_user(client, &format!("!{}", error_message)).await - } - - async fn send_to_user(client: &mut NamedPipeClient, message: &str) -> Result<()> { - let _ = send_message_with_client(client, message).await?; - Ok(()) - } - - fn is_admin() -> bool { - unsafe { IsUserAnAdmin().as_bool() } - } - - fn decrypt_data_base64(data_base64: &str, expect_appb: bool) -> Result { - dbg_log!("Decrypting data base64: {}", data_base64); - - let data = general_purpose::STANDARD.decode(data_base64).map_err(|e| { - dbg_log!("Failed to decode base64: {} APPB: {}", e, expect_appb); - e - })?; - - let decrypted = decrypt_data(&data, expect_appb)?; - let decrypted_base64 = general_purpose::STANDARD.encode(decrypted); - - Ok(decrypted_base64) - } - - fn decrypt_data(data: &[u8], expect_appb: bool) -> Result> { - if expect_appb && !data.starts_with(b"APPB") { - dbg_log!("Decoded data does not start with 'APPB'"); - return Err(anyhow!("Decoded data does not start with 'APPB'")); - } - - let data = if expect_appb { &data[4..] } else { data }; - - let in_blob = CRYPT_INTEGER_BLOB { - cbData: data.len() as u32, - pbData: data.as_ptr() as *mut u8, - }; - - let mut out_blob = CRYPT_INTEGER_BLOB { - cbData: 0, - pbData: ptr::null_mut(), - }; - - let result = unsafe { - CryptUnprotectData( - &in_blob, - None, - None, - None, - None, - CRYPTPROTECT_UI_FORBIDDEN, - &mut out_blob, - ) - }; - - if result.is_ok() && !out_blob.pbData.is_null() && out_blob.cbData > 0 { - let decrypted = unsafe { - std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize).to_vec() - }; - - // Free the memory allocated by CryptUnprotectData - unsafe { LocalFree(Some(HLOCAL(out_blob.pbData as *mut _))) }; - - Ok(decrypted) - } else { - dbg_log!("CryptUnprotectData failed"); - Err(anyhow!("CryptUnprotectData failed")) - } - } - - // - // Impersonate a SYSTEM process - // - - fn start_impersonating() -> Result { - // Need to enable SE_DEBUG_PRIVILEGE to enumerate and open SYSTEM processes - enable_debug_privilege()?; - - // Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication, so try several. - let (token, pid, name) = find_system_process_with_token(get_system_pid_list())?; - - // Impersonate the SYSTEM process - unsafe { - ImpersonateLoggedOnUser(token)?; - }; - dbg_log!("Impersonating system process '{}' (PID: {})", name, pid); - - Ok(token) - } - - fn stop_impersonating(token: HANDLE) -> Result<()> { - unsafe { - RevertToSelf()?; - CloseHandle(token)?; - }; - Ok(()) - } - - fn find_system_process_with_token( - pids: Vec<(u32, &'static str)>, - ) -> Result<(HANDLE, u32, &'static str)> { - for (pid, name) in pids { - match get_system_token_from_pid(pid) { - Err(_) => { - dbg_log!( - "Failed to open process handle '{}' (PID: {}), skipping", - name, - pid - ); - continue; - } - Ok(system_handle) => { - return Ok((system_handle, pid, name)); - } - } - } - Err(anyhow!("Failed to get system token from any process")) - } - - fn get_system_token_from_pid(pid: u32) -> Result { - let handle = get_process_handle(pid)?; - let token = get_system_token(handle)?; - unsafe { - CloseHandle(handle)?; - }; - Ok(token) - } - - fn get_system_token(handle: HANDLE) -> Result { - let token_handle = unsafe { - let mut token_handle = HANDLE::default(); - OpenProcessToken(handle, TOKEN_DUPLICATE | TOKEN_QUERY, &mut token_handle)?; - token_handle - }; - - let duplicate_token = unsafe { - let mut duplicate_token = HANDLE::default(); - DuplicateToken( - token_handle, - Security::SECURITY_IMPERSONATION_LEVEL(2), - &mut duplicate_token, - )?; - CloseHandle(token_handle)?; - duplicate_token - }; - - Ok(duplicate_token) - } - - fn get_system_pid_list() -> Vec<(u32, &'static str)> { - let sys = System::new_all(); - SYSTEM_PROCESS_NAMES - .iter() - .flat_map(|&name| { - sys.processes_by_exact_name(name.as_ref()) - .map(move |process| (process.pid().as_u32(), name)) - }) - .collect() - } - - fn get_process_handle(pid: u32) -> Result { - let hprocess = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?; - Ok(hprocess) - } - - #[link(name = "ntdll")] - unsafe extern "system" { - unsafe fn RtlAdjustPrivilege( - privilege: i32, - enable: BOOL, - current_thread: BOOL, - previous_value: *mut BOOL, - ) -> NTSTATUS; - } - - fn enable_debug_privilege() -> Result<()> { - let mut previous_value = BOOL(0); - let status = unsafe { - dbg_log!("Setting SE_DEBUG_PRIVILEGE to 1 via RtlAdjustPrivilege"); - RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, BOOL(1), BOOL(0), &mut previous_value) - }; - - match status { - STATUS_SUCCESS => { - dbg_log!( - "SE_DEBUG_PRIVILEGE set to 1, was {} before", - previous_value.as_bool() - ); - Ok(()) - } - _ => { - dbg_log!("RtlAdjustPrivilege failed with status: 0x{:X}", status.0); - Err(anyhow!("Failed to adjust privilege")) - } - } - } - - // - // Pipe - // - - async fn open_and_validate_pipe_server(pipe_name: &'static str) -> Result { - let client = open_pipe_client(pipe_name).await?; - - if ENABLE_SERVER_SIGNATURE_VALIDATION { - let server_pid = get_named_pipe_server_pid(&client)?; - dbg_log!("Connected to pipe server PID {}", server_pid); - - // Validate the server end process signature - let exe_path = resolve_process_executable_path(server_pid)?; - - dbg_log!("Pipe server executable path: {}", exe_path.display()); - - if !verify_signature(&exe_path)? { - return Err(anyhow!("Pipe server signature is not valid")); - } - - dbg_log!("Pipe server signature verified for PID {}", server_pid); - } - - Ok(client) - } - - fn run() -> Result { - dbg_log!("Starting bitwarden_chromium_import_helper.exe"); - - let args = Args::try_parse()?; - - if !is_admin() { - return Err(anyhow!("Expected to run with admin privileges")); - } - - dbg_log!("Running as ADMINISTRATOR"); - - // Impersonate a SYSTEM process to be able to decrypt data encrypted for the machine - let system_decrypted_base64 = { - let system_token = start_impersonating()?; - defer! { - dbg_log!("Stopping impersonation"); - _ = stop_impersonating(system_token); - } - let system_decrypted_base64 = decrypt_data_base64(&args.encrypted, true)?; - dbg_log!("Decrypted data with system"); - system_decrypted_base64 - }; - - // This is just to check that we're decrypting Chrome keys and not something else sent to us by a malicious actor. - // Now that we're back from SYSTEM, we need to decrypt one more time just to verify. - // Chrome keys are double encrypted: once at SYSTEM level and once at USER level. - // When the decryption fails, it means that we're decrypting something unexpected. - // We don't send this result back since the library will decrypt again at USER level. - - _ = decrypt_data_base64(&system_decrypted_base64, false).map_err(|e| { - dbg_log!("User level decryption check failed: {}", e); - e - })?; - - dbg_log!("User level decryption check passed"); - - Ok(system_decrypted_base64) - } - - fn init_logging(log_path: &Path, file_level: LevelFilter) { - // We only log to a file. It's impossible to see stdout/stderr when this exe is launched from ShellExecuteW. - match std::fs::File::create(log_path) { - Ok(file) => { - let file_filter = EnvFilter::builder() - .with_default_directive(file_level.into()) - .from_env_lossy(); - - let file_layer = fmt::layer() - .with_writer(file) - .with_ansi(false) - .with_filter(file_filter); - - tracing_subscriber::registry().with(file_layer).init(); - } - Err(error) => { - error!(%error, ?log_path, "Could not create log file."); - } - } - } - - pub(crate) async fn main() { - if ENABLE_DEVELOPER_LOGGING { - init_logging(LOG_FILENAME.as_ref(), LevelFilter::DEBUG); - } - - let mut client = match open_and_validate_pipe_server(ADMIN_TO_USER_PIPE_NAME).await { - Ok(client) => client, - Err(e) => { - error!( - "Failed to open pipe {} to send result/error: {}", - ADMIN_TO_USER_PIPE_NAME, e - ); - return; - } - }; - - match run() { - Ok(system_decrypted_base64) => { - dbg_log!("Sending response back to user"); - let _ = send_to_user(&mut client, &system_decrypted_base64).await; - } - Err(e) => { - dbg_log!("Error: {}", e); - send_error_to_user(&mut client, &format!("{}", e)).await; - } - } - } -} - -pub(crate) use windows_binary::*; diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/config.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/config.rs new file mode 100644 index 00000000000..cf05b4de524 --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/config.rs @@ -0,0 +1,2 @@ +// List of SYSTEM process names to try to impersonate +pub(crate) const SYSTEM_PROCESS_NAMES: [&str; 2] = ["services.exe", "winlogon.exe"]; diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs new file mode 100644 index 00000000000..094dbf94a67 --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs @@ -0,0 +1,278 @@ +use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit}; +use anyhow::{anyhow, Result}; +use base64::{engine::general_purpose, Engine as _}; +use chacha20poly1305::ChaCha20Poly1305; +use chromium_importer::chromium::crypt_unprotect_data; +use scopeguard::defer; +use tracing::debug; +use windows::{ + core::w, + Win32::Security::Cryptography::{ + self, NCryptOpenKey, NCryptOpenStorageProvider, CERT_KEY_SPEC, CRYPTPROTECT_UI_FORBIDDEN, + NCRYPT_FLAGS, NCRYPT_KEY_HANDLE, NCRYPT_PROV_HANDLE, NCRYPT_SILENT_FLAG, + }, +}; + +use super::impersonate::{start_impersonating, stop_impersonating}; + +// +// Base64 +// + +pub(crate) fn decode_base64(data_base64: &str) -> Result> { + debug!("Decoding base64 data: {}", data_base64); + + let data = general_purpose::STANDARD.decode(data_base64).map_err(|e| { + debug!("Failed to decode base64: {}", e); + e + })?; + + Ok(data) +} + +pub(crate) fn encode_base64(data: &[u8]) -> String { + general_purpose::STANDARD.encode(data) +} + +// +// DPAPI decryption +// + +pub(crate) fn decrypt_with_dpapi_as_system(encrypted: &[u8]) -> Result> { + // Impersonate a SYSTEM process to be able to decrypt data encrypted for the machine + let system_token = start_impersonating()?; + defer! { + debug!("Stopping impersonation"); + _ = stop_impersonating(system_token); + } + + decrypt_with_dpapi_as_user(encrypted, true) +} + +pub(crate) fn decrypt_with_dpapi_as_user(encrypted: &[u8], expect_appb: bool) -> Result> { + let system_decrypted = decrypt_with_dpapi(encrypted, expect_appb)?; + debug!( + "Decrypted data with SYSTEM {} bytes", + system_decrypted.len() + ); + + Ok(system_decrypted) +} + +fn decrypt_with_dpapi(data: &[u8], expect_appb: bool) -> Result> { + if expect_appb && (data.len() < 5 || !data.starts_with(b"APPB")) { + const ERR_MSG: &str = "Ciphertext is too short or does not start with 'APPB'"; + debug!("{}", ERR_MSG); + return Err(anyhow!(ERR_MSG)); + } + + let data = if expect_appb { &data[4..] } else { data }; + + crypt_unprotect_data(data, CRYPTPROTECT_UI_FORBIDDEN) +} + +// +// Chromium key decoding +// + +pub(crate) fn decode_abe_key_blob(blob_data: &[u8]) -> Result> { + // Parse and skip the header + let header_len = u32::from_le_bytes(get_safe(blob_data, 0, 4)?.try_into()?) as usize; + debug!("ABE key blob header length: {}", header_len); + + // Parse content length + let content_len_offset = 4 + header_len; + let content_len = + u32::from_le_bytes(get_safe(blob_data, content_len_offset, 4)?.try_into()?) as usize; + debug!("ABE key blob content length: {}", content_len); + + if content_len < 32 { + return Err(anyhow!( + "Corrupted ABE key blob: content length is less than 32" + )); + } + + let content_offset = content_len_offset + 4; + let content = get_safe(blob_data, content_offset, content_len)?; + + // When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds, Brave, possibly Edge + if content_len == 32 { + return Ok(content.to_vec()); + } + + let version = content[0]; + debug!("ABE key blob version: {}", version); + + let key_blob = &content[1..]; + match version { + // Google Chrome v1 key encrypted with a hardcoded AES key + 1_u8 => decrypt_abe_key_blob_chrome_aes(key_blob), + // Google Chrome v2 key encrypted with a hardcoded ChaCha20 key + 2_u8 => decrypt_abe_key_blob_chrome_chacha20(key_blob), + // Google Chrome v3 key encrypted with CNG APIs + 3_u8 => decrypt_abe_key_blob_chrome_cng(key_blob), + v => Err(anyhow!("Unsupported ABE key blob version: {}", v)), + } +} + +fn get_safe(data: &[u8], start: usize, len: usize) -> Result<&[u8]> { + let end = start + len; + data.get(start..end).ok_or_else(|| { + anyhow!( + "Corrupted ABE key blob: expected bytes {}..{}, got {}", + start, + end, + data.len() + ) + }) +} + +fn decrypt_abe_key_blob_chrome_aes(blob: &[u8]) -> Result> { + const GOOGLE_AES_KEY: &[u8] = &[ + 0xB3, 0x1C, 0x6E, 0x24, 0x1A, 0xC8, 0x46, 0x72, 0x8D, 0xA9, 0xC1, 0xFA, 0xC4, 0x93, 0x66, + 0x51, 0xCF, 0xFB, 0x94, 0x4D, 0x14, 0x3A, 0xB8, 0x16, 0x27, 0x6B, 0xCC, 0x6D, 0xA0, 0x28, + 0x47, 0x87, + ]; + let aes_key = Key::::from_slice(GOOGLE_AES_KEY); + let cipher = Aes256Gcm::new(aes_key); + + decrypt_abe_key_blob_with_aead(blob, &cipher, "v1 (AES flavor)") +} + +fn decrypt_abe_key_blob_chrome_chacha20(blob: &[u8]) -> Result> { + const GOOGLE_CHACHA20_KEY: &[u8] = &[ + 0xE9, 0x8F, 0x37, 0xD7, 0xF4, 0xE1, 0xFA, 0x43, 0x3D, 0x19, 0x30, 0x4D, 0xC2, 0x25, 0x80, + 0x42, 0x09, 0x0E, 0x2D, 0x1D, 0x7E, 0xEA, 0x76, 0x70, 0xD4, 0x1F, 0x73, 0x8D, 0x08, 0x72, + 0x96, 0x60, + ]; + + let chacha20_key = chacha20poly1305::Key::from_slice(GOOGLE_CHACHA20_KEY); + let cipher = ChaCha20Poly1305::new(chacha20_key); + + decrypt_abe_key_blob_with_aead(blob, &cipher, "v2 (ChaCha20 flavor)") +} + +fn decrypt_abe_key_blob_with_aead(blob: &[u8], cipher: &C, version: &str) -> Result> +where + C: Aead, +{ + if blob.len() < 60 { + return Err(anyhow!( + "Corrupted ABE key blob: expected at least 60 bytes, got {} bytes", + blob.len() + )); + } + + let iv = &blob[0..12]; + let ciphertext = &blob[12..12 + 48]; + + debug!("Google ABE {} detected: {:?} {:?}", version, iv, ciphertext); + + let decrypted = cipher + .decrypt(iv.into(), ciphertext) + .map_err(|e| anyhow!("Failed to decrypt v20 key with {}: {}", version, e))?; + + Ok(decrypted) +} + +fn decrypt_abe_key_blob_chrome_cng(blob: &[u8]) -> Result> { + if blob.len() < 92 { + return Err(anyhow!( + "Corrupted ABE key blob: expected at least 92 bytes, got {} bytes", + blob.len() + )); + } + + let encrypted_aes_key: [u8; 32] = blob[0..32].try_into()?; + let iv: [u8; 12] = blob[32..32 + 12].try_into()?; + let ciphertext: [u8; 48] = blob[44..44 + 48].try_into()?; + + debug!( + "Google ABE v3 (CNG flavor) detected: {:?} {:?} {:?}", + encrypted_aes_key, iv, ciphertext + ); + + // First, decrypt the AES key with CNG API + let decrypted_aes_key: Vec = { + let system_token = start_impersonating()?; + defer! { + debug!("Stopping impersonation"); + _ = stop_impersonating(system_token); + } + decrypt_with_cng(&encrypted_aes_key)? + }; + + const GOOGLE_XOR_KEY: [u8; 32] = [ + 0xCC, 0xF8, 0xA1, 0xCE, 0xC5, 0x66, 0x05, 0xB8, 0x51, 0x75, 0x52, 0xBA, 0x1A, 0x2D, 0x06, + 0x1C, 0x03, 0xA2, 0x9E, 0x90, 0x27, 0x4F, 0xB2, 0xFC, 0xF5, 0x9B, 0xA4, 0xB7, 0x5C, 0x39, + 0x23, 0x90, + ]; + + // XOR the decrypted AES key with the hardcoded key + let aes_key: Vec = decrypted_aes_key + .into_iter() + .zip(GOOGLE_XOR_KEY) + .map(|(a, b)| a ^ b) + .collect(); + + // Decrypt the actual ABE key with the decrypted AES key + let cipher = Aes256Gcm::new(aes_key.as_slice().into()); + let key = cipher + .decrypt((&iv).into(), ciphertext.as_ref()) + .map_err(|e| anyhow!("Failed to decrypt v20 key with AES-GCM: {}", e))?; + + Ok(key) +} + +fn decrypt_with_cng(ciphertext: &[u8]) -> Result> { + // 1. Open the cryptographic provider + let mut provider = NCRYPT_PROV_HANDLE::default(); + unsafe { + NCryptOpenStorageProvider( + &mut provider, + w!("Microsoft Software Key Storage Provider"), + 0, + )?; + }; + + // Don't forget to free the provider + defer!(unsafe { + _ = Cryptography::NCryptFreeObject(provider.into()); + }); + + // 2. Open the key + let mut key = NCRYPT_KEY_HANDLE::default(); + unsafe { + NCryptOpenKey( + provider, + &mut key, + w!("Google Chromekey1"), + CERT_KEY_SPEC::default(), + NCRYPT_FLAGS::default(), + )?; + }; + + // Don't forget to free the key + defer!(unsafe { + _ = Cryptography::NCryptFreeObject(key.into()); + }); + + // 3. Decrypt the data (assume the plaintext is not larger than the ciphertext) + let mut plaintext = vec![0; ciphertext.len()]; + let mut plaintext_len = 0; + unsafe { + Cryptography::NCryptDecrypt( + key, + ciphertext.into(), + None, + Some(&mut plaintext), + &mut plaintext_len, + NCRYPT_SILENT_FLAG, + )?; + }; + + // In case the plaintext is smaller than the ciphertext + plaintext.truncate(plaintext_len as usize); + + Ok(plaintext) +} diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs new file mode 100644 index 00000000000..5a5109b9d32 --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs @@ -0,0 +1,139 @@ +use anyhow::{anyhow, Result}; +use sysinfo::System; +use tracing::debug; +use windows::{ + core::BOOL, + Wdk::System::SystemServices::SE_DEBUG_PRIVILEGE, + Win32::{ + Foundation::{CloseHandle, HANDLE, NTSTATUS, STATUS_SUCCESS}, + Security::{ + self, DuplicateToken, ImpersonateLoggedOnUser, RevertToSelf, TOKEN_DUPLICATE, + TOKEN_QUERY, + }, + System::Threading::{OpenProcess, OpenProcessToken, PROCESS_QUERY_LIMITED_INFORMATION}, + }, +}; + +use super::config::SYSTEM_PROCESS_NAMES; + +#[link(name = "ntdll")] +unsafe extern "system" { + unsafe fn RtlAdjustPrivilege( + privilege: i32, + enable: BOOL, + current_thread: BOOL, + previous_value: *mut BOOL, + ) -> NTSTATUS; +} + +pub(crate) fn start_impersonating() -> Result { + // Need to enable SE_DEBUG_PRIVILEGE to enumerate and open SYSTEM processes + enable_debug_privilege()?; + + // Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication, so try several. + let (token, pid, name) = find_system_process_with_token(get_system_pid_list())?; + + // Impersonate the SYSTEM process + unsafe { + ImpersonateLoggedOnUser(token)?; + }; + debug!("Impersonating system process '{}' (PID: {})", name, pid); + + Ok(token) +} + +pub(crate) fn stop_impersonating(token: HANDLE) -> Result<()> { + unsafe { + RevertToSelf()?; + CloseHandle(token)?; + }; + Ok(()) +} + +fn find_system_process_with_token( + pids: Vec<(u32, &'static str)>, +) -> Result<(HANDLE, u32, &'static str)> { + for (pid, name) in pids { + match get_system_token_from_pid(pid) { + Err(_) => { + debug!( + "Failed to open process handle '{}' (PID: {}), skipping", + name, pid + ); + continue; + } + Ok(system_handle) => { + return Ok((system_handle, pid, name)); + } + } + } + Err(anyhow!("Failed to get system token from any process")) +} + +fn get_system_token_from_pid(pid: u32) -> Result { + let handle = get_process_handle(pid)?; + let token = get_system_token(handle)?; + unsafe { + CloseHandle(handle)?; + }; + Ok(token) +} + +fn get_system_token(handle: HANDLE) -> Result { + let token_handle = unsafe { + let mut token_handle = HANDLE::default(); + OpenProcessToken(handle, TOKEN_DUPLICATE | TOKEN_QUERY, &mut token_handle)?; + token_handle + }; + + let duplicate_token = unsafe { + let mut duplicate_token = HANDLE::default(); + DuplicateToken( + token_handle, + Security::SECURITY_IMPERSONATION_LEVEL(2), + &mut duplicate_token, + )?; + CloseHandle(token_handle)?; + duplicate_token + }; + + Ok(duplicate_token) +} + +fn get_system_pid_list() -> Vec<(u32, &'static str)> { + let sys = System::new_all(); + SYSTEM_PROCESS_NAMES + .iter() + .flat_map(|&name| { + sys.processes_by_exact_name(name.as_ref()) + .map(move |process| (process.pid().as_u32(), name)) + }) + .collect() +} + +fn get_process_handle(pid: u32) -> Result { + let hprocess = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?; + Ok(hprocess) +} + +fn enable_debug_privilege() -> Result<()> { + let mut previous_value = BOOL(0); + let status = unsafe { + debug!("Setting SE_DEBUG_PRIVILEGE to 1 via RtlAdjustPrivilege"); + RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, BOOL(1), BOOL(0), &mut previous_value) + }; + + match status { + STATUS_SUCCESS => { + debug!( + "SE_DEBUG_PRIVILEGE set to 1, was {} before", + previous_value.as_bool() + ); + Ok(()) + } + _ => { + debug!("RtlAdjustPrivilege failed with status: 0x{:X}", status.0); + Err(anyhow!("Failed to adjust privilege")) + } + } +} diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs new file mode 100644 index 00000000000..7ee34a4160e --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs @@ -0,0 +1,29 @@ +use tracing::{error, level_filters::LevelFilter}; +use tracing_subscriber::{ + fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, Layer as _, +}; + +use chromium_importer::config::{ENABLE_DEVELOPER_LOGGING, LOG_FILENAME}; + +pub(crate) fn init_logging() { + if ENABLE_DEVELOPER_LOGGING { + // We only log to a file. It's impossible to see stdout/stderr when this exe is launched from ShellExecuteW. + match std::fs::File::create(LOG_FILENAME) { + Ok(file) => { + let file_filter = EnvFilter::builder() + .with_default_directive(LevelFilter::DEBUG.into()) + .from_env_lossy(); + + let file_layer = fmt::layer() + .with_writer(file) + .with_ansi(false) + .with_filter(file_filter); + + tracing_subscriber::registry().with(file_layer).init(); + } + Err(error) => { + error!(%error, ?LOG_FILENAME, "Could not create log file."); + } + } + } +} diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs new file mode 100644 index 00000000000..e178a8accf7 --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs @@ -0,0 +1,225 @@ +use anyhow::{anyhow, Result}; +use clap::Parser; +use scopeguard::defer; +use std::{ + ffi::OsString, + os::windows::{ffi::OsStringExt as _, io::AsRawHandle}, + path::PathBuf, + time::Duration, +}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::windows::named_pipe::{ClientOptions, NamedPipeClient}, + time, +}; +use tracing::{debug, error}; +use windows::Win32::{ + Foundation::{CloseHandle, ERROR_PIPE_BUSY, HANDLE}, + System::{ + Pipes::GetNamedPipeServerProcessId, + Threading::{ + OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, + PROCESS_QUERY_LIMITED_INFORMATION, + }, + }, + UI::Shell::IsUserAnAdmin, +}; + +use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME}; + +use super::{ + crypto::{ + decode_abe_key_blob, decode_base64, decrypt_with_dpapi_as_system, + decrypt_with_dpapi_as_user, encode_base64, + }, + log::init_logging, +}; + +#[derive(Parser)] +#[command(name = "bitwarden_chromium_import_helper")] +#[command(about = "Admin tool for ABE service management")] +struct Args { + #[arg(long, help = "Base64 encoded encrypted data string")] + encrypted: String, +} + +async fn open_pipe_client(pipe_name: &'static str) -> Result { + let max_attempts = 5; + for _ in 0..max_attempts { + match ClientOptions::new().open(pipe_name) { + Ok(client) => { + debug!("Successfully connected to the pipe!"); + return Ok(client); + } + Err(e) if e.raw_os_error() == Some(ERROR_PIPE_BUSY.0 as i32) => { + debug!("Pipe is busy, retrying in 50ms..."); + } + Err(e) => { + debug!("Failed to connect to pipe: {}", &e); + return Err(e.into()); + } + } + + time::sleep(Duration::from_millis(50)).await; + } + + Err(anyhow!( + "Failed to connect to pipe after {} attempts", + max_attempts + )) +} + +async fn send_message_with_client(client: &mut NamedPipeClient, message: &str) -> Result { + client.write_all(message.as_bytes()).await?; + + // Try to receive a response for this message + let mut buffer = vec![0u8; 64 * 1024]; + match client.read(&mut buffer).await { + Ok(0) => Err(anyhow!( + "Server closed the connection (0 bytes read) on message" + )), + Ok(bytes_received) => { + let response = String::from_utf8_lossy(&buffer[..bytes_received]); + Ok(response.to_string()) + } + Err(e) => Err(anyhow!("Failed to receive response for message: {}", e)), + } +} + +fn get_named_pipe_server_pid(client: &NamedPipeClient) -> Result { + let handle = HANDLE(client.as_raw_handle() as _); + let mut pid: u32 = 0; + unsafe { GetNamedPipeServerProcessId(handle, &mut pid) }?; + Ok(pid) +} + +fn resolve_process_executable_path(pid: u32) -> Result { + debug!("Resolving process executable path for PID {}", pid); + + // Open the process handle + let hprocess = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) }?; + debug!("Opened process handle for PID {}", pid); + + // Close when no longer needed + defer! { + debug!("Closing process handle for PID {}", pid); + unsafe { + _ = CloseHandle(hprocess); + } + }; + + let mut exe_name = vec![0u16; 32 * 1024]; + let mut exe_name_length = exe_name.len() as u32; + unsafe { + QueryFullProcessImageNameW( + hprocess, + PROCESS_NAME_WIN32, + windows::core::PWSTR(exe_name.as_mut_ptr()), + &mut exe_name_length, + ) + }?; + debug!( + "QueryFullProcessImageNameW returned {} bytes", + exe_name_length + ); + + exe_name.truncate(exe_name_length as usize); + Ok(PathBuf::from(OsString::from_wide(&exe_name))) +} + +async fn send_error_to_user(client: &mut NamedPipeClient, error_message: &str) { + _ = send_to_user(client, &format!("!{}", error_message)).await +} + +async fn send_to_user(client: &mut NamedPipeClient, message: &str) -> Result<()> { + let _ = send_message_with_client(client, message).await?; + Ok(()) +} + +fn is_admin() -> bool { + unsafe { IsUserAnAdmin().as_bool() } +} + +async fn open_and_validate_pipe_server(pipe_name: &'static str) -> Result { + let client = open_pipe_client(pipe_name).await?; + + let server_pid = get_named_pipe_server_pid(&client)?; + debug!("Connected to pipe server PID {}", server_pid); + + // Validate the server end process signature + let exe_path = resolve_process_executable_path(server_pid)?; + + debug!("Pipe server executable path: {}", exe_path.display()); + + if !verify_signature(&exe_path)? { + return Err(anyhow!("Pipe server signature is not valid")); + } + + debug!("Pipe server signature verified for PID {}", server_pid); + + Ok(client) +} + +fn run() -> Result { + debug!("Starting bitwarden_chromium_import_helper.exe"); + + let args = Args::try_parse()?; + + if !is_admin() { + return Err(anyhow!("Expected to run with admin privileges")); + } + + debug!("Running as ADMINISTRATOR"); + + let encrypted = decode_base64(&args.encrypted)?; + debug!( + "Decoded encrypted data [{}] {:?}", + encrypted.len(), + encrypted + ); + + let system_decrypted = decrypt_with_dpapi_as_system(&encrypted)?; + debug!( + "Decrypted data with DPAPI as SYSTEM {} {:?}", + system_decrypted.len(), + system_decrypted + ); + + let user_decrypted = decrypt_with_dpapi_as_user(&system_decrypted, false)?; + debug!( + "Decrypted data with DPAPI as USER {} {:?}", + user_decrypted.len(), + user_decrypted + ); + + let key = decode_abe_key_blob(&user_decrypted)?; + debug!("Decoded ABE key blob {} {:?}", key.len(), key); + + Ok(encode_base64(&key)) +} + +pub(crate) async fn main() { + init_logging(); + + let mut client = match open_and_validate_pipe_server(ADMIN_TO_USER_PIPE_NAME).await { + Ok(client) => client, + Err(e) => { + error!( + "Failed to open pipe {} to send result/error: {}", + ADMIN_TO_USER_PIPE_NAME, e + ); + return; + } + }; + + match run() { + Ok(system_decrypted_base64) => { + debug!("Sending response back to user"); + let _ = send_to_user(&mut client, &system_decrypted_base64).await; + } + Err(e) => { + debug!("Error: {}", e); + send_error_to_user(&mut client, &format!("{}", e)).await; + } + } +} diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/mod.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/mod.rs new file mode 100644 index 00000000000..d745dc27618 --- /dev/null +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/mod.rs @@ -0,0 +1,7 @@ +mod config; +mod crypto; +mod impersonate; +mod log; +mod main; + +pub(crate) use main::main; diff --git a/apps/desktop/desktop_native/chromium_importer/Cargo.toml b/apps/desktop/desktop_native/chromium_importer/Cargo.toml index 51ad450a6fc..933b0a8dac3 100644 --- a/apps/desktop/desktop_native/chromium_importer/Cargo.toml +++ b/apps/desktop/desktop_native/chromium_importer/Cargo.toml @@ -7,7 +7,7 @@ publish = { workspace = true } [dependencies] aes = { workspace = true } -aes-gcm = "=0.10.3" +aes-gcm = { workspace = true } anyhow = { workspace = true } async-trait = "=0.1.88" base64 = { workspace = true } @@ -22,24 +22,13 @@ serde_json = { workspace = true } sha1 = "=0.10.6" tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] security-framework = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] -chacha20poly1305 = { workspace = true } windows = { workspace = true, features = [ - "Wdk_System_SystemServices", "Win32_Security_Cryptography", - "Win32_Security", - "Win32_Storage_FileSystem", - "Win32_System_IO", - "Win32_System_Memory", - "Win32_System_Pipes", - "Win32_System_ProcessStatus", - "Win32_System_Services", - "Win32_System_Threading", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", ] } diff --git a/apps/desktop/desktop_native/chromium_importer/README.md b/apps/desktop/desktop_native/chromium_importer/README.md index cec477c34a3..2a708ea572c 100644 --- a/apps/desktop/desktop_native/chromium_importer/README.md +++ b/apps/desktop/desktop_native/chromium_importer/README.md @@ -4,7 +4,7 @@ A rust library that allows you to directly import credentials from Chromium-base ## Windows ABE Architecture -On Windows chrome has additional protection measurements which needs to be circumvented in order to +On Windows Chrome has additional protection measurements which needs to be circumvented in order to get access to the passwords. ### Overview @@ -25,7 +25,9 @@ encryption scheme for some local profiles. The general idea of this encryption scheme is as follows: 1. Chrome generates a unique random encryption key. -2. This key is first encrypted at the **user level** with a fixed key. +2. This key is first encrypted at the **user level** with a fixed key for v1/v2 of ABE. For ABE v3 a more complicated + scheme is used that encrypts the key with a combination of a fixed key and a randomly generated key at the **system + level** via Windows CNG API. 3. It is then encrypted at the **user level** again using the Windows **Data Protection API (DPAPI)**. 4. Finally, it is sent to a special service that encrypts it with DPAPI at the **system level**. @@ -37,7 +39,7 @@ The following sections describe how the key is decrypted at each level. This is a Rust module that is part of the Chromium importer. It compiles and runs only on Windows (see `abe.rs` and `abe_config.rs`). Its main task is to launch `bitwarden_chromium_import_helper.exe` with elevated privileges, presenting -the user with the UAC prompt. See the `abe::decrypt_with_admin` call in `windows.rs`. +the user with the UAC prompt. See the `abe::decrypt_with_admin` call in `platform/windows/mod.rs`. This function takes two arguments: @@ -75,10 +77,26 @@ With the duplicated token, `ImpersonateLoggedOnUser` is called to impersonate a > **At this point `bitwarden_chromium_import_helper.exe` is running as SYSTEM.** -The received encryption key can now be decrypted using DPAPI at the system level. +The received encryption key can now be decrypted using DPAPI at the **system level**. -The decrypted result is sent back to the client via the named pipe. `bitwarden_chromium_import_helper.exe` connects to -the pipe and writes the result. +Next, the impersonation is stopped and the feshly decrypted key is decrypted at the **user level** with DPAPI one more +time. + +At this point, for browsers not using the custom encryption/obfuscation layer like unbranded Chromium, the twice +decrypted key is the actual encryption key that could be used to decrypt the stored passwords. + +For other browsers like Google Chrome, some additional processing is required. The decrypted key is actually a blob of structured data that could take multiple forms: + +1. exactly 32 bytes: plain key, nothing to be done more in this case +2. blob starts with 0x01: the key is encrypted with a fixed AES key found in Google Chrome binary, a random IV is stored + in the blob as well +3. blob starts with 0x02: the key is encrypted with a fixed ChaCha20 key found in Google Chrome binary, a random IV is + stored in the blob as well +4. blob starts with 0x03: the blob contains a random key, encrypted with CNG API with a random key stored in the + **system keychain** under the name `Google Chromekey1`. After that key is decryped (under **system level** impersonation again), the key is xor'ed with a fixed key from the Chrome binary and the it is used to decrypt the key from the last DPAPI decryption stage. + +The decrypted key is sent back to the client via the named pipe. `bitwarden_chromium_import_helper.exe` connects to the +pipe and writes the result. The response can indicate success or failure: @@ -92,17 +110,8 @@ Finally, `bitwarden_chromium_import_helper.exe` exits. ### 3. Back to the Client Library -The decrypted Base64-encoded string is returned from `bitwarden_chromium_import_helper.exe` to the named pipe server at -the user level. At this point it has been decrypted only once—at the system level. - -Next, the string is decrypted at the **user level** with DPAPI. - -Finally, for Google Chrome (but not Brave), it is decrypted again with a hard-coded key found in `elevation_service.exe` -from the Chrome installation. Based on the version of the encrypted string (encoded within the string itself), this step -uses either **AES-256-GCM** or **ChaCha20-Poly1305**. See `windows.rs` for details. - -After these steps, the master key is available and can be used to decrypt the password information stored in the -browser’s local database. +The decrypted Base64-encoded key is returned from `bitwarden_chromium_import_helper.exe` to the named pipe server at the +user level. The key is used to decrypt the stored passwords and notes. ### TL;DR Steps @@ -120,13 +129,12 @@ browser’s local database. 2. Ensure `SE_DEBUG_PRIVILEGE` is enabled (not strictly necessary in tests). 3. Impersonate a system process such as `services.exe` or `winlogon.exe`. 4. Decrypt the key using DPAPI at the **SYSTEM** level. + 5. Decrypt it again with DPAPI at the **USER** level. + 6. (For Chrome only) Decrypt again with the hard-coded key, possibly at the **system level** again (see above). 5. Send the result or error back via the named pipe. 6. Exit. 3. **Back on the client side:** - 1. Receive the encryption key. + 1. Receive the master key. 2. Shutdown the pipe server. - 3. Decrypt it with DPAPI at the **USER** level. - 4. (For Chrome only) Decrypt again with the hard-coded key. - 5. Obtain the fully decrypted master key. - 6. Use the master key to read and decrypt stored passwords from Chrome, Brave, Edge, etc. + 3. Use the master key to read and decrypt stored passwords from Chrome, Brave, Edge, etc. diff --git a/apps/desktop/desktop_native/chromium_importer/build.rs b/apps/desktop/desktop_native/chromium_importer/build.rs new file mode 100644 index 00000000000..5791e63f036 --- /dev/null +++ b/apps/desktop/desktop_native/chromium_importer/build.rs @@ -0,0 +1,15 @@ +include!("config_constants.rs"); + +fn main() { + println!("cargo:rerun-if-changed=config_constants.rs"); + + if cfg!(not(debug_assertions)) { + if ENABLE_DEVELOPER_LOGGING { + panic!("ENABLE_DEVELOPER_LOGGING must be false in release builds"); + } + + if !ENABLE_SIGNATURE_VALIDATION { + panic!("ENABLE_SIGNATURE_VALIDATION must be true in release builds"); + } + } +} diff --git a/apps/desktop/desktop_native/chromium_importer/config_constants.rs b/apps/desktop/desktop_native/chromium_importer/config_constants.rs new file mode 100644 index 00000000000..26397b13714 --- /dev/null +++ b/apps/desktop/desktop_native/chromium_importer/config_constants.rs @@ -0,0 +1,12 @@ +// Enable this to log to a file. The way this executable is used, it's not easy to debug and the stdout gets lost. +// This is intended for development time only. +pub const ENABLE_DEVELOPER_LOGGING: bool = false; + +// The absolute path to log file when developer logging is enabled +// Change this to a suitable path for your environment +pub const LOG_FILENAME: &str = "c:\\path\\to\\log.txt"; + +/// Ensure the signature of the helper and main binary is validated in production builds +/// +/// This must be true in release builds but may be disabled in debug builds for testing. +pub const ENABLE_SIGNATURE_VALIDATION: bool = true; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index 471e35da23e..aec8a84b5c1 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -10,9 +10,7 @@ use rusqlite::{params, Connection}; mod platform; #[cfg(target_os = "windows")] -pub use platform::{ - verify_signature, ADMIN_TO_USER_PIPE_NAME, EXPECTED_SIGNATURE_SHA256_THUMBPRINT, -}; +pub use platform::*; pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs new file mode 100644 index 00000000000..60f7b806033 --- /dev/null +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs @@ -0,0 +1,54 @@ +use anyhow::{anyhow, Result}; +use windows::Win32::{ + Foundation::{LocalFree, HLOCAL}, + Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB}, +}; + +/// Rust friendly wrapper around CryptUnprotectData +/// +/// Decrypts the data passed in using the `CryptUnprotectData` api. +pub fn crypt_unprotect_data(data: &[u8], flags: u32) -> Result> { + if data.is_empty() { + return Ok(Vec::new()); + } + + let data_in = CRYPT_INTEGER_BLOB { + cbData: data.len() as u32, + pbData: data.as_ptr() as *mut u8, + }; + + let mut data_out = CRYPT_INTEGER_BLOB::default(); + + let result = unsafe { + CryptUnprotectData( + &data_in, + None, // ppszDataDescr: Option<*mut PWSTR> + None, // pOptionalEntropy: Option<*const CRYPT_INTEGER_BLOB> + None, // pvReserved: Option<*const std::ffi::c_void> + None, // pPromptStruct: Option<*const CRYPTPROTECT_PROMPTSTRUCT> + flags, // dwFlags: u32 + &mut data_out, + ) + }; + + if result.is_err() { + return Err(anyhow!("CryptUnprotectData failed")); + } + + if data_out.pbData.is_null() || data_out.cbData == 0 { + return Ok(Vec::new()); + } + + let output_slice = + unsafe { std::slice::from_raw_parts(data_out.pbData, data_out.cbData as usize) }; + + // SAFETY: Must copy data before calling LocalFree() below. + // Calling to_vec() after LocalFree() causes use-after-free bugs. + let output = output_slice.to_vec(); + + unsafe { + LocalFree(Some(HLOCAL(data_out.pbData as *mut _))); + } + + Ok(output) +} diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs index a8045cf1182..867104d9bfd 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs @@ -2,20 +2,17 @@ use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; -use chacha20poly1305::ChaCha20Poly1305; use std::path::{Path, PathBuf}; -use windows::Win32::{ - Foundation::{LocalFree, HLOCAL}, - Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB}, -}; use crate::chromium::{BrowserConfig, CryptoService, LocalState}; use crate::util; mod abe; mod abe_config; +mod crypto; mod signature; pub use abe_config::ADMIN_TO_USER_PIPE_NAME; +pub use crypto::*; pub use signature::*; // @@ -62,9 +59,6 @@ pub(crate) fn get_crypto_service( const ADMIN_EXE_FILENAME: &str = "bitwarden_chromium_import_helper.exe"; -// This should be enabled for production -const ENABLE_SIGNATURE_VALIDATION: bool = true; - // // CryptoService // @@ -170,7 +164,7 @@ impl WindowsCryptoService { return Err(anyhow!("Encrypted master key is not encrypted with DPAPI")); } - let key = unprotect_data_win(&key_bytes[5..]) + let key = crypt_unprotect_data(&key_bytes[5..], 0) .map_err(|e| anyhow!("Failed to unprotect the master key: {}", e))?; Ok(key) @@ -185,7 +179,7 @@ impl WindowsCryptoService { let admin_exe_path = get_admin_exe_path()?; - if ENABLE_SIGNATURE_VALIDATION && !verify_signature(&admin_exe_path)? { + if !verify_signature(&admin_exe_path)? { return Err(anyhow!("Helper executable signature is not valid")); } @@ -208,167 +202,9 @@ impl WindowsCryptoService { )); } - let key_bytes = BASE64_STANDARD.decode(&key_base64)?; - let key = unprotect_data_win(&key_bytes)?; - - Self::decode_abe_key_blob(key.as_slice()) + let key = BASE64_STANDARD.decode(&key_base64)?; + Ok(key) } - - fn decode_abe_key_blob(blob_data: &[u8]) -> Result> { - let header_len = u32::from_le_bytes(blob_data[0..4].try_into()?) as usize; - // Ignore the header - - let content_len_offset = 4 + header_len; - let content_len = - u32::from_le_bytes(blob_data[content_len_offset..content_len_offset + 4].try_into()?) - as usize; - - if content_len < 1 { - return Err(anyhow!( - "Corrupted ABE key blob: content length is less than 1" - )); - } - - let content_offset = content_len_offset + 4; - let content = &blob_data[content_offset..content_offset + content_len]; - - // When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds, Brave, possibly Edge - if content_len == 32 { - return Ok(content.to_vec()); - } - - let version = content[0]; - let key_blob = &content[1..]; - match version { - // Google Chrome v1 key encrypted with a hardcoded AES key - 1_u8 => Self::decrypt_abe_key_blob_chrome_aes(key_blob), - // Google Chrome v2 key encrypted with a hardcoded ChaCha20 key - 2_u8 => Self::decrypt_abe_key_blob_chrome_chacha20(key_blob), - // Google Chrome v3 key encrypted with CNG APIs - 3_u8 => Self::decrypt_abe_key_blob_chrome_cng(key_blob), - v => Err(anyhow!("Unsupported ABE key blob version: {}", v)), - } - } - - // TODO: DRY up with decrypt_abe_key_blob_chrome_chacha20 - fn decrypt_abe_key_blob_chrome_aes(blob: &[u8]) -> Result> { - if blob.len() < 60 { - return Err(anyhow!( - "Corrupted ABE key blob: expected at least 60 bytes, got {} bytes", - blob.len() - )); - } - - let iv: [u8; 12] = blob[0..12].try_into()?; - let ciphertext: [u8; 48] = blob[12..12 + 48].try_into()?; - - const GOOGLE_AES_KEY: &[u8] = &[ - 0xB3, 0x1C, 0x6E, 0x24, 0x1A, 0xC8, 0x46, 0x72, 0x8D, 0xA9, 0xC1, 0xFA, 0xC4, 0x93, - 0x66, 0x51, 0xCF, 0xFB, 0x94, 0x4D, 0x14, 0x3A, 0xB8, 0x16, 0x27, 0x6B, 0xCC, 0x6D, - 0xA0, 0x28, 0x47, 0x87, - ]; - let aes_key = Key::::from_slice(GOOGLE_AES_KEY); - let cipher = Aes256Gcm::new(aes_key); - - let decrypted = cipher - .decrypt((&iv).into(), ciphertext.as_ref()) - .map_err(|e| anyhow!("Failed to decrypt v20 key with Google AES key: {}", e))?; - - Ok(decrypted) - } - - fn decrypt_abe_key_blob_chrome_chacha20(blob: &[u8]) -> Result> { - if blob.len() < 60 { - return Err(anyhow!( - "Corrupted ABE key blob: expected at least 60 bytes, got {} bytes", - blob.len() - )); - } - - let chacha20_key = chacha20poly1305::Key::from_slice(GOOGLE_CHACHA20_KEY); - let cipher = ChaCha20Poly1305::new(chacha20_key); - - const GOOGLE_CHACHA20_KEY: &[u8] = &[ - 0xE9, 0x8F, 0x37, 0xD7, 0xF4, 0xE1, 0xFA, 0x43, 0x3D, 0x19, 0x30, 0x4D, 0xC2, 0x25, - 0x80, 0x42, 0x09, 0x0E, 0x2D, 0x1D, 0x7E, 0xEA, 0x76, 0x70, 0xD4, 0x1F, 0x73, 0x8D, - 0x08, 0x72, 0x96, 0x60, - ]; - - let iv: [u8; 12] = blob[0..12].try_into()?; - let ciphertext: [u8; 48] = blob[12..12 + 48].try_into()?; - - let decrypted = cipher - .decrypt((&iv).into(), ciphertext.as_ref()) - .map_err(|e| anyhow!("Failed to decrypt v20 key with Google ChaCha20 key: {}", e))?; - - Ok(decrypted) - } - - fn decrypt_abe_key_blob_chrome_cng(blob: &[u8]) -> Result> { - if blob.len() < 92 { - return Err(anyhow!( - "Corrupted ABE key blob: expected at least 92 bytes, got {} bytes", - blob.len() - )); - } - - let _encrypted_aes_key: [u8; 32] = blob[0..32].try_into()?; - let _iv: [u8; 12] = blob[32..32 + 12].try_into()?; - let _ciphertext: [u8; 48] = blob[44..44 + 48].try_into()?; - - // TODO: Decrypt the AES key using CNG APIs - // TODO: Implement this in the future once we run into a browser that uses this scheme - - // There's no way to test this at the moment. This encryption scheme is not used in any of the browsers I've tested. - Err(anyhow!("Google ABE CNG flavor is not supported yet")) - } -} - -fn unprotect_data_win(data: &[u8]) -> Result> { - if data.is_empty() { - return Ok(Vec::new()); - } - - let data_in = CRYPT_INTEGER_BLOB { - cbData: data.len() as u32, - pbData: data.as_ptr() as *mut u8, - }; - - let mut data_out = CRYPT_INTEGER_BLOB { - cbData: 0, - pbData: std::ptr::null_mut(), - }; - - let result = unsafe { - CryptUnprotectData( - &data_in, - None, // ppszDataDescr: Option<*mut PWSTR> - None, // pOptionalEntropy: Option<*const CRYPT_INTEGER_BLOB> - None, // pvReserved: Option<*const std::ffi::c_void> - None, // pPromptStruct: Option<*const CRYPTPROTECT_PROMPTSTRUCT> - 0, // dwFlags: u32 - &mut data_out, - ) - }; - - if result.is_err() { - return Err(anyhow!("CryptUnprotectData failed")); - } - - if data_out.pbData.is_null() || data_out.cbData == 0 { - return Ok(Vec::new()); - } - - let output_slice = - unsafe { std::slice::from_raw_parts(data_out.pbData, data_out.cbData as usize) }; - - unsafe { - if !data_out.pbData.is_null() { - LocalFree(Some(HLOCAL(data_out.pbData as *mut std::ffi::c_void))); - } - } - - Ok(output_slice.to_vec()) } fn get_admin_exe_path() -> Result { diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs index a30b396db28..d5d6c5d6d15 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs @@ -3,10 +3,20 @@ use std::path::Path; use tracing::{debug, info}; use verifysign::CodeSignVerifier; +use crate::config::ENABLE_SIGNATURE_VALIDATION; + pub const EXPECTED_SIGNATURE_SHA256_THUMBPRINT: &str = "9f6680c4720dbf66d1cb8ed6e328f58e42523badc60d138c7a04e63af14ea40d"; pub fn verify_signature(path: &Path) -> Result { + if !ENABLE_SIGNATURE_VALIDATION { + info!( + "Signature validation is disabled. Skipping verification for: {}", + path.display() + ); + return Ok(true); + } + info!("verifying signature of: {}", path.display()); let verifier = CodeSignVerifier::for_file(path) diff --git a/apps/desktop/desktop_native/chromium_importer/src/lib.rs b/apps/desktop/desktop_native/chromium_importer/src/lib.rs index d92515c39f9..d03e4cdf496 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/lib.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/lib.rs @@ -1,5 +1,9 @@ #![doc = include_str!("../README.md")] +pub mod config { + include!("../config_constants.rs"); +} + pub mod chromium; pub mod metadata; mod util; diff --git a/apps/desktop/desktop_native/clippy.toml b/apps/desktop/desktop_native/clippy.toml index a29e019ac02..4441a038635 100644 --- a/apps/desktop/desktop_native/clippy.toml +++ b/apps/desktop/desktop_native/clippy.toml @@ -1,2 +1,10 @@ allow-unwrap-in-tests=true allow-expect-in-tests=true + +disallowed-macros = [ + { path = "log::trace", reason = "Use tracing for logging needs", replacement = "tracing::trace" }, + { path = "log::debug", reason = "Use tracing for logging needs", replacement = "tracing::debug" }, + { path = "log::info", reason = "Use tracing for logging needs", replacement = "tracing::info" }, + { path = "log::warn", reason = "Use tracing for logging needs", replacement = "tracing::warn" }, + { path = "log::error", reason = "Use tracing for logging needs", replacement = "tracing::error" }, +] diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 97a8b7d545a..ea44f3d9a27 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -21,12 +21,11 @@ serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } tokio-util = { workspace = true } tracing = { workspace = true } -tracing-oslog = "0.3.0" tracing-subscriber = { workspace = true } uniffi = { workspace = true, features = ["cli"] } [target.'cfg(target_os = "macos")'.dependencies] -oslog = { workspace = true } +tracing-oslog = "0.3.0" [build-dependencies] uniffi = { workspace = true, features = ["build"] } diff --git a/apps/desktop/desktop_native/macos_provider/src/lib.rs b/apps/desktop/desktop_native/macos_provider/src/lib.rs index 359fe213996..a5a134b0bfe 100644 --- a/apps/desktop/desktop_native/macos_provider/src/lib.rs +++ b/apps/desktop/desktop_native/macos_provider/src/lib.rs @@ -1,4 +1,5 @@ #![cfg(target_os = "macos")] +#![allow(clippy::disallowed_macros)] // uniffi macros trip up clippy's evaluation use std::{ collections::HashMap, diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index 0b18786863d..eaa299db508 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -20,7 +20,7 @@ "**/node_modules/@bitwarden/desktop-napi/index.js", "**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node" ], - "electronVersion": "36.9.3", + "electronVersion": "37.7.0", "generateUpdatesFilesForAllChannels": true, "publish": { "provider": "generic", diff --git a/apps/desktop/fastlane/fastfile b/apps/desktop/fastlane/fastfile index 08c35dfa7b3..134d18563de 100644 --- a/apps/desktop/fastlane/fastfile +++ b/apps/desktop/fastlane/fastfile @@ -21,11 +21,13 @@ platform :mac do .split('.') .map(&:strip) .reject(&:empty?) - .map { |item| "• #{item}" } + .map { |item| "• #{item.gsub(/\A(?:•|\u2022)\s*/, '')}" } .join("\n") - UI.message("Original changelog: #{changelog[0,100]}#{changelog.length > 100 ? '...' : ''}") - UI.message("Formatted changelog: #{formatted_changelog[0,100]}#{formatted_changelog.length > 100 ? '...' : ''}") + UI.message("Original changelog: ") + UI.message("#{changelog}") + UI.message("Formatted changelog: ") + UI.message("#{formatted_changelog}") # Create release notes directories and files for all locales APP_CONFIG[:locales].each do |locale| diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index b6e402a3ef6..a4286aabed9 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -19,7 +19,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.18.11", + "@types/node": "22.19.0", "typescript": "5.4.2" } }, @@ -117,9 +117,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", - "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", "license": "MIT", "peer": true, "dependencies": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 285997f6482..55699af47dd 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -24,7 +24,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.18.11", + "@types/node": "22.19.0", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 19ab9e783d4..519aae2c6b8 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.10.2", + "version": "2025.11.0", "keywords": [ "bitwarden", "password", @@ -40,7 +40,7 @@ "pack:dir": "npm run clean:dist && electron-builder --dir -p never", "pack:lin:flatpak": "flatpak-builder --repo=../../.flatpak-repo ../../.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ../../.flatpak-repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", - "pack:lin:arm64": "npm run clean:dist && electron-builder --dir -p never && tar -czvf ./dist/bitwarden_desktop_arm64.tar.gz -C ./dist/linux-arm64-unpacked/ .", + "pack:lin:arm64": "npm run clean:dist && electron-builder --linux --arm64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snap pack --compression=lzo ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/ && tar -czvf ./dist/bitwarden_desktop_arm64.tar.gz -C ./dist/linux-arm64-unpacked/ .", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:with-extension": "npm run clean:dist && npm run build:macos-extension:mac && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index 4b6dcab0dff..6243ba1e538 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -32,6 +32,7 @@ import { ModalService } from "@bitwarden/angular/services/modal.service"; import { FingerprintDialogComponent } from "@bitwarden/auth/angular"; import { DESKTOP_SSO_CALLBACK, + LockService, LogoutReason, UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; @@ -195,6 +196,7 @@ export class AppComponent implements OnInit, OnDestroy { private pinService: PinServiceAbstraction, private readonly tokenService: TokenService, private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy, + private readonly lockService: LockService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); @@ -245,7 +247,7 @@ export class AppComponent implements OnInit, OnDestroy { // eslint-disable-next-line @typescript-eslint/no-floating-promises this.updateAppMenu(); await this.systemService.clearPendingClipboard(); - await this.processReloadService.startProcessReload(this.authService); + await this.processReloadService.startProcessReload(); break; case "authBlocked": // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -258,21 +260,10 @@ export class AppComponent implements OnInit, OnDestroy { this.loading = false; break; case "lockVault": - await this.vaultTimeoutService.lock(message.userId); + await this.lockService.lock(message.userId); break; case "lockAllVaults": { - const currentUser = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a.id)), - ); - const accounts = await firstValueFrom(this.accountService.accounts$); - await this.vaultTimeoutService.lock(currentUser); - for (const account of Object.keys(accounts)) { - if (account === currentUser) { - continue; - } - - await this.vaultTimeoutService.lock(account); - } + await this.lockService.lockAll(); break; } case "locked": @@ -286,12 +277,12 @@ export class AppComponent implements OnInit, OnDestroy { } await this.updateAppMenu(); await this.systemService.clearPendingClipboard(); - await this.processReloadService.startProcessReload(this.authService); + await this.processReloadService.startProcessReload(); break; case "startProcessReload": // 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.processReloadService.startProcessReload(this.authService); + this.processReloadService.startProcessReload(); break; case "cancelProcessReload": this.processReloadService.cancelProcessReload(); @@ -736,8 +727,6 @@ export class AppComponent implements OnInit, OnDestroy { } } - await this.updateAppMenu(); - // This must come last otherwise the logout will prematurely trigger // a process reload before all the state service user data can be cleaned up this.authService.logOut(async () => {}, userBeingLoggedOut); @@ -814,11 +803,9 @@ export class AppComponent implements OnInit, OnDestroy { } const options = await this.getVaultTimeoutOptions(userId); if (options[0] === timeout) { - // 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("vaultTimeout", userId as UserId) - : await this.vaultTimeoutService.lock(userId); + ? await this.logOut("vaultTimeout", userId as UserId) + : await this.lockService.lock(userId as UserId); } } } diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index d17ebb5b942..e94aaf83183 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -132,7 +132,7 @@ export class AvatarComponent implements OnChanges, OnInit { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - 'Roboto,"Helvetica Neue",Helvetica,Arial,' + + 'Inter,"Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); textTag.textContent = character; diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index a0ee33a459c..be91c309875 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -77,7 +77,10 @@ import { LogService as LogServiceAbstraction, } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { + PlatformUtilsService, + PlatformUtilsService as PlatformUtilsServiceAbstraction, +} from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service"; @@ -262,6 +265,7 @@ const safeProviders: SafeProvider[] = [ BiometricStateService, AccountServiceAbstraction, LogService, + AuthServiceAbstraction, ], }), safeProvider({ @@ -336,6 +340,7 @@ const safeProviders: SafeProvider[] = [ ConfigService, Fido2AuthenticatorServiceAbstraction, AccountService, + PlatformUtilsService, ], }), safeProvider({ diff --git a/apps/desktop/src/app/tools/import/import-desktop.component.ts b/apps/desktop/src/app/tools/import/import-desktop.component.ts index dd34855f416..6b1d26562fc 100644 --- a/apps/desktop/src/app/tools/import/import-desktop.component.ts +++ b/apps/desktop/src/app/tools/import/import-desktop.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DialogRef, AsyncActionsModule, ButtonModule, DialogModule } from "@bitwarden/components"; +import type { chromium_importer } from "@bitwarden/desktop-napi"; import { ImportMetadataServiceAbstraction } from "@bitwarden/importer-core"; import { ImportComponent, @@ -47,11 +48,14 @@ export class ImportDesktopComponent { this.dialogRef.close(); } - protected onLoadProfilesFromBrowser(browser: string): Promise { + protected onLoadProfilesFromBrowser(browser: string): Promise { return ipc.tools.chromiumImporter.getAvailableProfiles(browser); } - protected onImportFromBrowser(browser: string, profile: string): Promise { + protected onImportFromBrowser( + browser: string, + profile: string, + ): Promise { return ipc.tools.chromiumImporter.importLogins(browser, profile); } } diff --git a/apps/desktop/src/app/tools/preload.ts b/apps/desktop/src/app/tools/preload.ts index c21a1ac0bfc..ff0a4ffbbd8 100644 --- a/apps/desktop/src/app/tools/preload.ts +++ b/apps/desktop/src/app/tools/preload.ts @@ -5,9 +5,12 @@ import type { chromium_importer } from "@bitwarden/desktop-napi"; const chromiumImporter = { getMetadata: (): Promise> => ipcRenderer.invoke("chromium_importer.getMetadata"), - getAvailableProfiles: (browser: string): Promise => + getAvailableProfiles: (browser: string): Promise => ipcRenderer.invoke("chromium_importer.getAvailableProfiles", browser), - importLogins: (browser: string, profileId: string): Promise => + importLogins: ( + browser: string, + profileId: string, + ): Promise => ipcRenderer.invoke("chromium_importer.importLogins", browser, profileId), }; diff --git a/apps/desktop/src/app/tools/send/add-edit.component.ts b/apps/desktop/src/app/tools/send/add-edit.component.ts index b817adda848..076b0f6c9d5 100644 --- a/apps/desktop/src/app/tools/send/add-edit.component.ts +++ b/apps/desktop/src/app/tools/send/add-edit.component.ts @@ -19,14 +19,23 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CalloutModule, DialogService, ToastService } from "@bitwarden/components"; +import { DesktopPremiumUpgradePromptService } from "../../../services/desktop-premium-upgrade-prompt.service"; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "app-send-add-edit", templateUrl: "add-edit.component.html", imports: [CommonModule, JslibModule, ReactiveFormsModule, CalloutModule], + providers: [ + { + provide: PremiumUpgradePromptService, + useClass: DesktopPremiumUpgradePromptService, + }, + ], }) export class AddEditComponent extends BaseAddEditComponent { constructor( @@ -45,6 +54,7 @@ export class AddEditComponent extends BaseAddEditComponent { billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, toastService: ToastService, + premiumUpgradePromptService: PremiumUpgradePromptService, ) { super( i18nService, @@ -62,6 +72,7 @@ export class AddEditComponent extends BaseAddEditComponent { billingAccountProfileStateService, accountService, toastService, + premiumUpgradePromptService, ); } diff --git a/apps/desktop/src/auth/components/set-pin.component.html b/apps/desktop/src/auth/components/set-pin.component.html index 6fb5829b79a..aaebf7c1cdb 100644 --- a/apps/desktop/src/auth/components/set-pin.component.html +++ b/apps/desktop/src/auth/components/set-pin.component.html @@ -1,6 +1,6 @@ -
    +
    {{ "unlockWithPin" | i18n }}
    diff --git a/apps/desktop/src/autofill/components/autotype-shortcut.component.html b/apps/desktop/src/autofill/components/autotype-shortcut.component.html index 774c299e0b6..6f73d4006ac 100644 --- a/apps/desktop/src/autofill/components/autotype-shortcut.component.html +++ b/apps/desktop/src/autofill/components/autotype-shortcut.component.html @@ -1,6 +1,6 @@ -
    +
    {{ "typeShortcut" | i18n }}
    diff --git a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts index 09f03d2ef8e..e33ab0d4c3b 100644 --- a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts @@ -5,6 +5,7 @@ import { LogService } from "@bitwarden/logging"; import { WindowMain } from "../../main/window.main"; import { stringIsNotUndefinedNullAndEmpty } from "../../utils"; +import { AutotypeVaultData } from "../models/autotype-vault-data"; import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut"; export class MainDesktopAutotypeService { @@ -47,18 +48,12 @@ export class MainDesktopAutotypeService { } }); - ipcMain.on("autofill.completeAutotypeRequest", (event, data) => { - const { response } = data; - + ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => { if ( - stringIsNotUndefinedNullAndEmpty(response.username) && - stringIsNotUndefinedNullAndEmpty(response.password) + stringIsNotUndefinedNullAndEmpty(vaultData.username) && + stringIsNotUndefinedNullAndEmpty(vaultData.password) ) { - this.doAutotype( - response.username, - response.password, - this.autotypeKeyboardShortcut.getArrayFormat(), - ); + this.doAutotype(vaultData, this.autotypeKeyboardShortcut.getArrayFormat()); } }); } @@ -89,8 +84,9 @@ export class MainDesktopAutotypeService { : this.logService.info("Enabling autotype failed."); } - private doAutotype(username: string, password: string, keyboardShortcut: string[]) { - const inputPattern = username + "\t" + password; + private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) { + const TAB = "\t"; + const inputPattern = vaultData.username + TAB + vaultData.password; const inputArray = new Array(inputPattern.length); for (let i = 0; i < inputPattern.length; i++) { diff --git a/apps/desktop/src/autofill/models/autotype-vault-data.ts b/apps/desktop/src/autofill/models/autotype-vault-data.ts new file mode 100644 index 00000000000..ee3db98c334 --- /dev/null +++ b/apps/desktop/src/autofill/models/autotype-vault-data.ts @@ -0,0 +1,8 @@ +/** + * Vault data used in autotype operations. + * `username` and `password` are guaranteed to be not null/undefined. + */ +export interface AutotypeVaultData { + username: string; + password: string; +} diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index fcb2f646743..22b5cdf9463 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -5,6 +5,8 @@ import type { autofill } from "@bitwarden/desktop-napi"; import { Command } from "../platform/main/autofill/command"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; +import { AutotypeVaultData } from "./models/autotype-vault-data"; + export default { runCommand: (params: RunCommandParams): Promise> => ipcRenderer.invoke("autofill.runCommand", params), @@ -133,10 +135,7 @@ export default { listenAutotypeRequest: ( fn: ( windowTitle: string, - completeCallback: ( - error: Error | null, - response: { username?: string; password?: string }, - ) => void, + completeCallback: (error: Error | null, response: AutotypeVaultData | null) => void, ) => void, ) => { ipcRenderer.on( @@ -149,7 +148,7 @@ export default { ) => { const { windowTitle } = data; - fn(windowTitle, (error, response) => { + fn(windowTitle, (error, vaultData) => { if (error) { ipcRenderer.send("autofill.completeError", { windowTitle, @@ -157,11 +156,9 @@ export default { }); return; } - - ipcRenderer.send("autofill.completeAutotypeRequest", { - windowTitle, - response, - }); + if (vaultData !== null) { + ipcRenderer.send("autofill.completeAutotypeRequest", vaultData); + } }); }, ); diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 5500bc58f5a..18f4652d72a 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -13,6 +13,7 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getOptionalUserId } from "@bitwarden/common/auth/services/account.service"; +import { DeviceType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -24,6 +25,7 @@ import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction, } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; 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 { parseCredentialId } from "@bitwarden/common/platform/services/fido2/credential-id-utils"; import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils"; @@ -53,9 +55,15 @@ export class DesktopAutofillService implements OnDestroy { private configService: ConfigService, private fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction, private accountService: AccountService, + private platformUtilsService: PlatformUtilsService, ) {} async init() { + // Currently only supported for MacOS + if (this.platformUtilsService.getDevice() !== DeviceType.MacOsDesktop) { + return; + } + this.configService .getFeatureFlag$(FeatureFlag.MacOsNativeCredentialSync) .pipe( diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts new file mode 100644 index 00000000000..30cc800dd28 --- /dev/null +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts @@ -0,0 +1,50 @@ +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { getAutotypeVaultData } from "./desktop-autotype.service"; + +describe("getAutotypeVaultData", () => { + it("should return vault data when cipher has username and password", () => { + const cipherView = new CipherView(); + cipherView.login.username = "foo"; + cipherView.login.password = "bar"; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(error).toBeNull(); + expect(vaultData?.username).toEqual("foo"); + expect(vaultData?.password).toEqual("bar"); + }); + + it("should return error when firstCipher is undefined", () => { + const cipherView = undefined; + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("No matching vault item."); + }); + + it("should return error when username is undefined", () => { + const cipherView = new CipherView(); + cipherView.login.username = undefined; + cipherView.login.password = "bar"; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("Vault item is undefined."); + }); + + it("should return error when password is undefined", () => { + const cipherView = new CipherView(); + cipherView.login.username = "foo"; + cipherView.login.password = undefined; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("Vault item is undefined."); + }); +}); diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.ts index 24ec3907a62..7ee889e7b81 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.ts @@ -17,6 +17,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { UserId } from "@bitwarden/user-core"; +import { AutotypeVaultData } from "../models/autotype-vault-data"; + import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service"; export const defaultWindowsAutotypeKeyboardShortcut: string[] = ["Control", "Shift", "B"]; @@ -27,6 +29,8 @@ export const AUTOTYPE_ENABLED = new KeyDefinition( { deserializer: (b) => b }, ); +export type Result = [E, null] | [null, T]; + /* Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z @@ -63,11 +67,8 @@ export class DesktopAutotypeService { ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => { const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle); const firstCipher = possibleCiphers?.at(0); - - return callback(null, { - username: firstCipher?.login?.username, - password: firstCipher?.login?.password, - }); + const [error, vaultData] = getAutotypeVaultData(firstCipher); + callback(error, vaultData); }); } @@ -176,3 +177,23 @@ export class DesktopAutotypeService { return possibleCiphers; } } + +/** + * @return an `AutotypeVaultData` object or an `Error` if the + * cipher or vault data within are undefined. + */ +export function getAutotypeVaultData( + cipherView: CipherView | undefined, +): Result { + if (!cipherView) { + return [Error("No matching vault item."), null]; + } else if (cipherView.login.username === undefined || cipherView.login.password === undefined) { + return [Error("Vault item is undefined."), null]; + } else { + const vaultData: AutotypeVaultData = { + username: cipherView.login.username, + password: cipherView.login.password, + }; + return [null, vaultData]; + } +} diff --git a/apps/desktop/src/images/loading.svg b/apps/desktop/src/images/loading.svg index 5f4102a5921..e05a42f6c70 100644 --- a/apps/desktop/src/images/loading.svg +++ b/apps/desktop/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts index 0fe3d7e95e7..04a2f389781 100644 --- a/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/desktop.biometrics.service.ts @@ -18,4 +18,7 @@ export abstract class DesktopBiometricsService extends BiometricsService { /* Enables the v2 biometrics re-write. This will stay enabled until the application is restarted. */ abstract enableWindowsV2Biometrics(): Promise; abstract isWindowsV2BiometricsEnabled(): Promise; + /* Enables the v2 biometrics re-write. This will stay enabled until the application is restarted. */ + abstract enableLinuxV2Biometrics(): Promise; + abstract isLinuxV2BiometricsEnabled(): Promise; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts index 24bb5495da0..db7c7c8f7fa 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics-ipc.listener.ts @@ -62,6 +62,10 @@ export class MainBiometricsIPCListener { return await this.biometricService.enableWindowsV2Biometrics(); case BiometricAction.IsWindowsV2Enabled: return await this.biometricService.isWindowsV2BiometricsEnabled(); + case BiometricAction.EnableLinuxV2: + return await this.biometricService.enableLinuxV2Biometrics(); + case BiometricAction.IsLinuxV2Enabled: + return await this.biometricService.isLinuxV2BiometricsEnabled(); default: return; } diff --git a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts index d1aff17646a..da532828314 100644 --- a/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/main-biometrics.service.ts @@ -10,13 +10,14 @@ import { BiometricsStatus, BiometricStateService } from "@bitwarden/key-manageme import { WindowMain } from "../../main/window.main"; import { DesktopBiometricsService } from "./desktop.biometrics.service"; -import { WindowsBiometricsSystem } from "./native-v2"; +import { LinuxBiometricsSystem, WindowsBiometricsSystem } from "./native-v2"; import { OsBiometricService } from "./os-biometrics.service"; export class MainBiometricsService extends DesktopBiometricsService { private osBiometricsService: OsBiometricService; private shouldAutoPrompt = true; private windowsV2BiometricsEnabled = false; + private linuxV2BiometricsEnabled = false; constructor( private i18nService: I18nService, @@ -170,4 +171,16 @@ export class MainBiometricsService extends DesktopBiometricsService { async isWindowsV2BiometricsEnabled(): Promise { return this.windowsV2BiometricsEnabled; } + + async enableLinuxV2Biometrics(): Promise { + if (this.platform === "linux" && !this.linuxV2BiometricsEnabled) { + this.logService.info("[BiometricsMain] Loading native biometrics module v2 for linux"); + this.osBiometricsService = new LinuxBiometricsSystem(); + this.linuxV2BiometricsEnabled = true; + } + } + + async isLinuxV2BiometricsEnabled(): Promise { + return this.linuxV2BiometricsEnabled; + } } diff --git a/apps/desktop/src/key-management/biometrics/native-v2/index.ts b/apps/desktop/src/key-management/biometrics/native-v2/index.ts index 030224bbd74..94de850b759 100644 --- a/apps/desktop/src/key-management/biometrics/native-v2/index.ts +++ b/apps/desktop/src/key-management/biometrics/native-v2/index.ts @@ -1 +1,2 @@ export { default as WindowsBiometricsSystem } from "./os-biometrics-windows.service"; +export { default as LinuxBiometricsSystem } from "./os-biometrics-linux.service"; diff --git a/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.spec.ts b/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.spec.ts new file mode 100644 index 00000000000..91e2caba0cb --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.spec.ts @@ -0,0 +1,96 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { biometrics_v2, passwords } from "@bitwarden/desktop-napi"; +import { BiometricsStatus } from "@bitwarden/key-management"; + +import OsBiometricsServiceLinux from "./os-biometrics-linux.service"; + +jest.mock("@bitwarden/desktop-napi", () => ({ + biometrics_v2: { + initBiometricSystem: jest.fn(() => "mockSystem"), + provideKey: jest.fn(), + unenroll: jest.fn(), + unlock: jest.fn(), + authenticate: jest.fn(), + authenticateAvailable: jest.fn(), + unlockAvailable: jest.fn(), + }, + passwords: { + isAvailable: jest.fn(), + }, +})); + +const mockKey = new Uint8Array(64); + +jest.mock("../../../utils", () => ({ + isFlatpak: jest.fn(() => false), + isLinux: jest.fn(() => true), + isSnapStore: jest.fn(() => false), +})); + +describe("OsBiometricsServiceLinux", () => { + const userId = "user-id" as UserId; + const key = { toEncoded: () => ({ buffer: Buffer.from(mockKey) }) } as SymmetricCryptoKey; + let service: OsBiometricsServiceLinux; + + beforeEach(() => { + service = new OsBiometricsServiceLinux(); + jest.clearAllMocks(); + }); + + it("should set biometric key", async () => { + await service.setBiometricKey(userId, key); + expect(biometrics_v2.provideKey).toHaveBeenCalled(); + }); + + it("should delete biometric key", async () => { + await service.deleteBiometricKey(userId); + expect(biometrics_v2.unenroll).toHaveBeenCalled(); + }); + + it("should get biometric key", async () => { + (biometrics_v2.unlock as jest.Mock).mockResolvedValue(mockKey); + const result = await service.getBiometricKey(userId); + expect(result).toBeInstanceOf(SymmetricCryptoKey); + }); + + it("should return null if no biometric key", async () => { + (biometrics_v2.unlock as jest.Mock).mockResolvedValue(null); + const result = await service.getBiometricKey(userId); + expect(result).toBeNull(); + }); + + it("should authenticate biometric", async () => { + (biometrics_v2.authenticate as jest.Mock).mockResolvedValue(true); + const result = await service.authenticateBiometric(); + expect(result).toBe(true); + }); + + it("should check if biometrics is supported", async () => { + (passwords.isAvailable as jest.Mock).mockResolvedValue(true); + const result = await service.supportsBiometrics(); + expect(result).toBe(true); + }); + + it("should check if setup is needed", async () => { + (biometrics_v2.authenticateAvailable as jest.Mock).mockResolvedValue(false); + const result = await service.needsSetup(); + expect(result).toBe(true); + }); + + it("should check if can auto setup", async () => { + const result = await service.canAutoSetup(); + expect(result).toBe(true); + }); + + it("should get biometrics first unlock status for user", async () => { + (biometrics_v2.unlockAvailable as jest.Mock).mockResolvedValue(true); + const result = await service.getBiometricsFirstUnlockStatusForUser(userId); + expect(result).toBe(BiometricsStatus.Available); + }); + + it("should return false for hasPersistentKey", async () => { + const result = await service.hasPersistentKey(userId); + expect(result).toBe(false); + }); +}); diff --git a/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.ts b/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.ts new file mode 100644 index 00000000000..110db23ec79 --- /dev/null +++ b/apps/desktop/src/key-management/biometrics/native-v2/os-biometrics-linux.service.ts @@ -0,0 +1,118 @@ +import { spawn } from "child_process"; + +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { biometrics_v2, passwords } from "@bitwarden/desktop-napi"; +import { BiometricsStatus } from "@bitwarden/key-management"; + +import { isSnapStore, isFlatpak, isLinux } from "../../../utils"; +import { OsBiometricService } from "../os-biometrics.service"; + +const polkitPolicy = ` + + + + + Unlock Bitwarden + Authenticate to unlock Bitwarden + + no + no + auth_self + + +`; +const policyFileName = "com.bitwarden.Bitwarden.policy"; +const policyPath = "/usr/share/polkit-1/actions/"; + +export default class OsBiometricsServiceLinux implements OsBiometricService { + private biometricsSystem: biometrics_v2.BiometricLockSystem; + + constructor() { + this.biometricsSystem = biometrics_v2.initBiometricSystem(); + } + + async setBiometricKey(userId: UserId, key: SymmetricCryptoKey): Promise { + await biometrics_v2.provideKey( + this.biometricsSystem, + userId, + Buffer.from(key.toEncoded().buffer), + ); + } + + async deleteBiometricKey(userId: UserId): Promise { + await biometrics_v2.unenroll(this.biometricsSystem, userId); + } + + async getBiometricKey(userId: UserId): Promise { + const result = await biometrics_v2.unlock(this.biometricsSystem, userId, Buffer.from("")); + return result ? new SymmetricCryptoKey(Uint8Array.from(result)) : null; + } + + async authenticateBiometric(): Promise { + return await biometrics_v2.authenticate( + this.biometricsSystem, + Buffer.from(""), + "Authenticate to unlock", + ); + } + + async supportsBiometrics(): Promise { + // We assume all linux distros have some polkit implementation + // that either has bitwarden set up or not, which is reflected in osBiomtricsNeedsSetup. + // Snap does not have access at the moment to polkit + // This could be dynamically detected on dbus in the future. + // We should check if a libsecret implementation is available on the system + // because otherwise we cannot offlod the protected userkey to secure storage. + return await passwords.isAvailable(); + } + + async needsSetup(): Promise { + if (isSnapStore()) { + return false; + } + + // check whether the polkit policy is loaded via dbus call to polkit + return !(await biometrics_v2.authenticateAvailable(this.biometricsSystem)); + } + + async canAutoSetup(): Promise { + // We cannot auto setup on snap or flatpak since the filesystem is sandboxed. + // The user needs to manually set up the polkit policy outside of the sandbox + // since we allow access to polkit via dbus for the sandboxed clients, the authentication works from + // the sandbox, once the policy is set up outside of the sandbox. + return isLinux() && !isSnapStore() && !isFlatpak(); + } + + async runSetup(): Promise { + const process = spawn("pkexec", [ + "bash", + "-c", + `echo '${polkitPolicy}' > ${policyPath + policyFileName} && chown root:root ${policyPath + policyFileName} && chcon system_u:object_r:usr_t:s0 ${policyPath + policyFileName}`, + ]); + + await new Promise((resolve, reject) => { + process.on("close", (code) => { + if (code !== 0) { + reject("Failed to set up polkit policy"); + } else { + resolve(null); + } + }); + }); + } + + async getBiometricsFirstUnlockStatusForUser(userId: UserId): Promise { + return (await biometrics_v2.unlockAvailable(this.biometricsSystem, userId)) + ? BiometricsStatus.Available + : BiometricsStatus.UnlockNeeded; + } + + async enrollPersistent(userId: UserId, key: SymmetricCryptoKey): Promise {} + + async hasPersistentKey(userId: UserId): Promise { + return false; + } +} diff --git a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts index bc3631ad1b8..63d2225b7c6 100644 --- a/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts +++ b/apps/desktop/src/key-management/biometrics/renderer-biometrics.service.ts @@ -84,4 +84,12 @@ export class RendererBiometricsService extends DesktopBiometricsService { async isWindowsV2BiometricsEnabled(): Promise { return await ipc.keyManagement.biometric.isWindowsV2BiometricsEnabled(); } + + async enableLinuxV2Biometrics(): Promise { + return await ipc.keyManagement.biometric.enableLinuxV2Biometrics(); + } + + async isLinuxV2BiometricsEnabled(): Promise { + return await ipc.keyManagement.biometric.isLinuxV2BiometricsEnabled(); + } } diff --git a/apps/desktop/src/key-management/preload.ts b/apps/desktop/src/key-management/preload.ts index a9565790b86..d317b1f6ce0 100644 --- a/apps/desktop/src/key-management/preload.ts +++ b/apps/desktop/src/key-management/preload.ts @@ -69,6 +69,14 @@ const biometric = { ipcRenderer.invoke("biometric", { action: BiometricAction.IsWindowsV2Enabled, } satisfies BiometricMessage), + enableLinuxV2Biometrics: (): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.EnableLinuxV2, + } satisfies BiometricMessage), + isLinuxV2BiometricsEnabled: (): Promise => + ipcRenderer.invoke("biometric", { + action: BiometricAction.IsLinuxV2Enabled, + } satisfies BiometricMessage), }; export default { diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 0a3cbd229cd..6da1c7e9c8b 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Pasgemaakte omgewing" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index 7636c30576b..a6a7e881db9 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "مرحبًا بعودتك" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "يجب عليك إضافة رابط الخادم الأساسي أو على الأقل بيئة مخصصة." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "بيئة مخصصة" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 37761036c3e..81b24cadfe8 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Bu elementə düzəliş etmə icazəniz yoxdur" + }, "welcomeBack": { "message": "Yenidən xoş gəlmisiniz" }, @@ -772,7 +775,7 @@ "message": "Vahid daxil olma üsulunu istifadə et" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Təşkilatınız, vahid daxil olma tələb edir." }, "submit": { "message": "Göndər" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-lər, HTTPS istifadə etməlidir." + }, "customEnvironment": { "message": "Özəl mühit" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kart nömrəsi" + }, + "upgradeNow": { + "message": "İndi yüksəlt" + }, + "builtInAuthenticator": { + "message": "Daxili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvənli fayl anbarı" + }, + "emergencyAccess": { + "message": "Fövqəladə hal erişimi" + }, + "breachMonitoring": { + "message": "Pozuntu monitorinqi" + }, + "andMoreFeatures": { + "message": "Və daha çoxu!" + }, + "planDescPremium": { + "message": "Tam onlayn təhlükəsizlik" + }, + "upgradeToPremium": { + "message": "\"Premium\"a yüksəlt" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index 2e5a58e0e24..cead61915ca 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index 03b6c4d5090..c2c2e236d37 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Нямате право за редактиране на този елемент" + }, "welcomeBack": { "message": "Добре дошли отново" }, @@ -772,7 +775,7 @@ "message": "Използване на еднократна идентификация" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Вашата организация изисква еднократно удостоверяване." }, "submit": { "message": "Подаване" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." }, + "selfHostedEnvMustUseHttps": { + "message": "Адресите трябва да ползват HTTPS." + }, "customEnvironment": { "message": "Специална среда" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Номер на картата" + }, + "upgradeNow": { + "message": "Надграждане сега" + }, + "builtInAuthenticator": { + "message": "Вграден удостоверител" + }, + "secureFileStorage": { + "message": "Сигурно съхранение на файлове" + }, + "emergencyAccess": { + "message": "Авариен достъп" + }, + "breachMonitoring": { + "message": "Наблюдение за пробиви" + }, + "andMoreFeatures": { + "message": "И още!" + }, + "planDescPremium": { + "message": "Пълна сигурност в Интернет" + }, + "upgradeToPremium": { + "message": "Надградете до Платения план" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index e47df9a26cb..5c932d4ed21 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 5b453c176dc..08793959da6 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index bb3dc27d957..8ee8f7030bc 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Benvingut/da de nou" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorn personalitzat" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index f20911eceb7..578d0607cc2 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Nemáte oprávnění upravit tuto položku" + }, "welcomeBack": { "message": "Vítejte zpět" }, @@ -772,7 +775,7 @@ "message": "Použít jednotné přihlášení" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Vaše organizace vyžaduje jednotné přihlášení." }, "submit": { "message": "Odeslat" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." }, + "selfHostedEnvMustUseHttps": { + "message": "URL adresy musí používat HTTPS." + }, "customEnvironment": { "message": "Vlastní prostředí" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Číslo karty" + }, + "upgradeNow": { + "message": "Aktualizovat nyní" + }, + "builtInAuthenticator": { + "message": "Vestavěný autentifikátor" + }, + "secureFileStorage": { + "message": "Zabezpečené úložiště souborů" + }, + "emergencyAccess": { + "message": "Nouzový přístup" + }, + "breachMonitoring": { + "message": "Sledování úniků" + }, + "andMoreFeatures": { + "message": "A ještě více!" + }, + "planDescPremium": { + "message": "Dokončit online zabezpečení" + }, + "upgradeToPremium": { + "message": "Aktualizovat na Premium" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index af0a7029865..278196f9d04 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index b1e1ad6d201..fad9b9c1af4 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Velkommen tilbage" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Der skal tilføjes enten basis Server-URL'en eller mindst ét tilpasset miljø." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 580c9f42313..3f5caa00c4c 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" + }, "welcomeBack": { "message": "Willkommen zurück" }, @@ -772,7 +775,7 @@ "message": "Single Sign-on verwenden" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Deine Organisation erfordert Single Sign-On." }, "submit": { "message": "Absenden" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs müssen HTTPS verwenden." + }, "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kartennummer" + }, + "upgradeNow": { + "message": "Jetzt upgraden" + }, + "builtInAuthenticator": { + "message": "Integrierter Authenticator" + }, + "secureFileStorage": { + "message": "Sicherer Dateispeicher" + }, + "emergencyAccess": { + "message": "Notfallzugriff" + }, + "breachMonitoring": { + "message": "Datendiebstahl-Überwachung" + }, + "andMoreFeatures": { + "message": "Und mehr!" + }, + "planDescPremium": { + "message": "Umfassende Online-Sicherheit" + }, + "upgradeToPremium": { + "message": "Upgrade auf Premium" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 232c8448d98..55a3c4fe170 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Καλωσορίσατε και πάλι" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Πρέπει να προσθέσετε είτε το βασικό URL του διακομιστή ή τουλάχιστον ένα προσαρμοσμένο περιβάλλον." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 70242b6674f..da8d9ea0e34 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -4193,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index a2c96c63f51..63e0cf96742 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -772,7 +775,7 @@ "message": "Use single sign-on" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Your organisation requires single sign-on." }, "submit": { "message": "Submit" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index f746504d8e7..832025c8c0e 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -772,7 +775,7 @@ "message": "Use single sign-on" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Your organisation requires single sign-on." }, "submit": { "message": "Submit" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index 44fdd715cbd..a3a8643a8f6 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Bonrevenon!" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Propra medio" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 0e9b137c2b1..01163c7ad29 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Bienvenido de nuevo" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes añadir o bien la URL del servidor base, o al menos un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 3bf585f0351..25489871b3b 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Tere tulemast tagasi" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Sa pead lisama serveri nime (base URL) või vähemalt ühe iseseadistatud keskkonna." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kohandatud keskkond" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index a79964b304b..c1007d7d71c 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ingurune pertsonalizatua" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index 24e45b6cac0..eb62c711628 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "خوش آمدید" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "محیط سفارشی" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index c95934a1f36..06f0338e1f3 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Tervetuloa takaisin" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Sinun on lisättävä joko palvelimen perusosoite tai ainakin yksi mukautettu palvelinympäristö." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 317f8808af7..6a32df33ecb 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kapaligirang Custom" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index ddab6285e01..0cd10d0582c 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Content de vous revoir" }, @@ -772,7 +775,7 @@ "message": "Utiliser l'authentification unique" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Votre organisation exige l’authentification unique." }, "submit": { "message": "Soumettre" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Vous devez ajouter soit l'URL du serveur de base, soit au moins un environnement personnalisé." }, + "selfHostedEnvMustUseHttps": { + "message": "Les URL doivent utiliser le protocole HTTPS." + }, "customEnvironment": { "message": "Environnement personnalisé" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Numéro de carte" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index c6856f3375a..70d4c7cb494 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index dc41911950b..abe445f83d5 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "אין לך הרשאות לערוך את הפריט הזה" + }, "welcomeBack": { "message": "ברוך שובך" }, @@ -772,7 +775,7 @@ "message": "השתמש בכניסה יחידה" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "הארגון שלך דורש כניסה יחידה." }, "submit": { "message": "שלח" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, + "selfHostedEnvMustUseHttps": { + "message": "כתובות URL מוכרחות להשתמש ב־HTTPS." + }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, @@ -1226,7 +1232,7 @@ "message": "סיסמה ראשית שגויה" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "סיסמה ראשית אינה תקינה. יש לאשר שהדוא\"ל שלך נכון ושהחשבון שלך נוצר ב־$HOST$.", "placeholders": { "host": { "content": "$1", @@ -1856,10 +1862,10 @@ "message": "נעל בעזרת הסיסמה הראשית בהפעלה מחדש" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "דרוש סיסמה ראשית או PIN בעת הפעלה מחדש של היישום" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "דרוש סיסמה ראשית בעת הפעלה מחדש של היישום" }, "deleteAccount": { "message": "מחק חשבון" @@ -2559,7 +2565,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "פסק זמן מותאם אישית מינימלי הוא דקה 1." }, "inviteAccepted": { "message": "ההזמנה התקבלה" @@ -2676,7 +2682,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא.", "placeholders": { "organization": { "content": "$1", @@ -2685,7 +2691,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא. פריטי האוספים שלי לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -4123,13 +4129,13 @@ "message": "Bitwarden לא מאמת את מקומות הקלט, נא לוודא שזה החלון והשדה הנכונים בטרם שימוש בקיצור הדרך." }, "typeShortcut": { - "message": "Type shortcut" + "message": "הקלד קיצור דרך" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "כלול אחד או שניים ממקשי הצירוף הבאים: Ctrl, Alt, Win, או Shift, ואות." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "קיצור דרך לא חוקי" }, "moreBreadcrumbs": { "message": "עוד סימני דרך", @@ -4145,50 +4151,74 @@ "message": "אשר" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "הפעל קיצור דרך להקלדה אוטומטית (תצוגה תכונה מקדימה)" }, "enableAutotypeShortcutDescription": { - "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." + "message": "וודא שאתה נמצא בשדה הנכון לפני השימוש בקיצור הדרך כדי להימנע ממילוי נתונים במקום הלא נכון." }, "editShortcut": { "message": "ערוך קיצור דרך" }, "archiveNoun": { - "message": "Archive", + "message": "ארכיון", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "העבר לארכיון", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "הסר מהארכיון" }, "itemsInArchive": { - "message": "Items in archive" + "message": "פריטים בארכיון" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "אין פריטים בארכיון" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "הפריט נשלח לארכיון" }, "itemWasUnarchived": { - "message": "Item was unarchived" + "message": "הפריט הוסר מהארכיון" }, "archiveItem": { - "message": "Archive item" + "message": "העבר פריט לארכיון" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריט זה לארכיון?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "מיקוד" }, "cardNumberLabel": { - "message": "Card number" + "message": "מספר כרטיס" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 5a4895e20a1..84676c4d941 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index fd2cc31685e..d1c2ba68779 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Nemaš dozvolu za uređivanje ove stavke" + }, "welcomeBack": { "message": "Dobro došli natrag" }, @@ -772,7 +775,7 @@ "message": "Jedinstvena prijava (SSO)" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tvoja organizacija zahtijeva jedinstvenu prijavu." }, "submit": { "message": "Pošalji" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Moraš dodati ili osnovni URL poslužitelja ili barem jedno prilagođeno okruženje." }, + "selfHostedEnvMustUseHttps": { + "message": "URL mora koristiti HTTPS." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -4186,9 +4192,33 @@ "message": "Arhivirane stavke biti će izuzete iz rezultata općih pretraga i preporuka auto-ispune. Sigurno želiš arhivirati?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Poštanski broj" }, "cardNumberLabel": { - "message": "Card number" + "message": "Broj kartice" + }, + "upgradeNow": { + "message": "Nadogradi sada" + }, + "builtInAuthenticator": { + "message": "Ugrađeni autentifikator" + }, + "secureFileStorage": { + "message": "Sigurna pohrana datoteka" + }, + "emergencyAccess": { + "message": "Pristup u nuždi" + }, + "breachMonitoring": { + "message": "Nadzor proboja" + }, + "andMoreFeatures": { + "message": "I više!" + }, + "planDescPremium": { + "message": "Dovrši online sigurnost" + }, + "upgradeToPremium": { + "message": " Nadogradi na Premium" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index c0918cfba4b..9d296a7d2cc 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Nincs jogosulltság ezen elem szerkesztéséhez." + }, "welcomeBack": { "message": "Üdvözlet újra" }, @@ -772,7 +775,7 @@ "message": "Egyszeri bejelentkezés használata" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A szervezet egyszeri bejelentkezést igényel." }, "submit": { "message": "Beküldés" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." }, + "selfHostedEnvMustUseHttps": { + "message": "A webcímeknek HTTPS-t kell használniuk." + }, "customEnvironment": { "message": "Egyedi környezet" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kártya szám" + }, + "upgradeNow": { + "message": "Áttérés most" + }, + "builtInAuthenticator": { + "message": "Beépített hitelesítő" + }, + "secureFileStorage": { + "message": "Biztonságos fájl tárolás" + }, + "emergencyAccess": { + "message": "Sürgősségi hozzáférés" + }, + "breachMonitoring": { + "message": "Adatszivárgás figyelés" + }, + "andMoreFeatures": { + "message": "És még több!" + }, + "planDescPremium": { + "message": "Teljes körű online biztonság" + }, + "upgradeToPremium": { + "message": "Áttérés Prémium csomagra" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index f41c13de593..03da4bbd030 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Lingkungan Kustom" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 39501439da9..4881d96b44a 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Bentornato" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizzato" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 543ac4027f7..1dfb5a42ead 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "ようこそ" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "カスタム環境" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index fec326e1158..edaa68e7302 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index c6856f3375a..70d4c7cb494 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index c33d2ca3d4e..b880c845f4f 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 2c84d22640b..7ef1645febf 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "돌아온 것을 환영합니다" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "사용자 지정 환경" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index f053f0806ca..471bde7b410 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Individualizuota aplinka" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index 6fb441b1f7b..cc6cad0fd40 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" + }, "welcomeBack": { "message": "Laipni lūdzam atpakaļ" }, @@ -772,7 +775,7 @@ "message": "Izmantot vienoto pieteikšanos" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tava apvienība pieprasa vienoto pieteikšanos." }, "submit": { "message": "Iesniegt" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Jāpievieno vai no servera pamata URL vai vismaz viena pielāgota vide." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Pielāgota vide" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kartes numurs" + }, + "upgradeNow": { + "message": "Uzlabot tagad" + }, + "builtInAuthenticator": { + "message": "Iebūvēts autentificētājs" + }, + "secureFileStorage": { + "message": "Droša datņu krātuve" + }, + "emergencyAccess": { + "message": "Ārkārtas piekļuve" + }, + "breachMonitoring": { + "message": "Noplūžu pārraudzīšana" + }, + "andMoreFeatures": { + "message": "Un vēl!" + }, + "planDescPremium": { + "message": "Pilnīga drošība tiešsaistē" + }, + "upgradeToPremium": { + "message": "Uzlabot uz Premium" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index 4d1779b5cd0..a67fa99079b 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 14b215bc346..4eee4cb0c0d 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index c6856f3375a..70d4c7cb494 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 7bd62d93bac..047f272b564 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 0047f866933..9bede02bfcf 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Velkommen tilbake" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 1f7b8ab49e1..093896ca17a 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index 28aaa851d42..de0820ac5ed 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Je hebt geen toestemming om dit item te bewerken" + }, "welcomeBack": { "message": "Welkom terug" }, @@ -772,7 +775,7 @@ "message": "Single sign-on gebruiken" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Je organisatie vereist single sign-on." }, "submit": { "message": "Opslaan" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Je moet de basisserver-URL of ten minste één aangepaste omgeving toevoegen." }, + "selfHostedEnvMustUseHttps": { + "message": "URL's moeten HTTPS gebruiken." + }, "customEnvironment": { "message": "Aangepaste omgeving" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kaartnummer" + }, + "upgradeNow": { + "message": "Nu upgraden" + }, + "builtInAuthenticator": { + "message": "Ingebouwde authenticator" + }, + "secureFileStorage": { + "message": "Beveiligde bestandsopslag" + }, + "emergencyAccess": { + "message": "Noodtoegang" + }, + "breachMonitoring": { + "message": "Lek-monitoring" + }, + "andMoreFeatures": { + "message": "En meer!" + }, + "planDescPremium": { + "message": "Online beveiliging voltooien" + }, + "upgradeToPremium": { + "message": "Opwaarderen naar Premium" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index 899ce7cc927..c582356f115 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 1878cbc6a8b..89489e1db87 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index ef35183ccce..b9eb4b6232b 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Witaj ponownie" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Niestandardowe środowisko" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Numer karty" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 5113e99cacf..9749d280516 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -6,7 +6,7 @@ "message": "Filtros" }, "allItems": { - "message": "Todos os Itens" + "message": "Todos os itens" }, "favorites": { "message": "Favoritos" @@ -27,7 +27,7 @@ "message": "Anotação" }, "typeSecureNote": { - "message": "Nota Segura" + "message": "Anotação segura" }, "typeSshKey": { "message": "Chave SSH" @@ -42,10 +42,10 @@ "message": "Pesquisar no cofre" }, "resetSearch": { - "message": "Redefinir pesquisa" + "message": "Apagar pesquisa" }, "addItem": { - "message": "Adicionar Item" + "message": "Adicionar item" }, "shared": { "message": "Compartilhado" @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Boas-vindas de volta" }, @@ -79,7 +82,7 @@ "message": "Anexos" }, "viewItem": { - "message": "Ver Item" + "message": "Ver item" }, "name": { "message": "Nome" @@ -101,37 +104,37 @@ "message": "Novo URI" }, "username": { - "message": "Nome de Usuário" + "message": "Nome de usuário" }, "password": { "message": "Senha" }, "passphrase": { - "message": "Frase Secreta" + "message": "Frase secreta" }, "editItem": { - "message": "Editar Item" + "message": "Editar item" }, "emailAddress": { "message": "Endereço de e-mail" }, "verificationCodeTotp": { - "message": "Código de Verificação (TOTP)" + "message": "Código de verificação (TOTP)" }, "website": { "message": "Site" }, "notes": { - "message": "Notas" + "message": "Anotações" }, "customFields": { - "message": "Campos Personalizados" + "message": "Campos personalizados" }, "launch": { "message": "Abrir" }, "copyValue": { - "message": "Copiar Valor", + "message": "Copiar valor", "description": "Copy value to clipboard" }, "minimizeOnCopyToClipboard": { @@ -141,14 +144,14 @@ "message": "Minimizar ao copiar dados de um item para a área de transferência." }, "toggleVisibility": { - "message": "Alternar Visibilidade" + "message": "Habilitar visibilidade" }, "toggleCollapse": { - "message": "Alternar colapso", + "message": "Guardar/mostrar", "description": "Toggling an expand/collapse state." }, "cardholderName": { - "message": "Titular do Cartão" + "message": "Nome do titular do cartão" }, "number": { "message": "Número" @@ -160,22 +163,22 @@ "message": "Vencimento" }, "securityCode": { - "message": "Código de Segurança" + "message": "Código de segurança" }, "identityName": { - "message": "Nome de Identidade" + "message": "Nome da identidade" }, "company": { "message": "Empresa" }, "ssn": { - "message": "Número de Segurança Social" + "message": "Cadastro de Pessoas Físicas (CPF)" }, "passportNumber": { - "message": "Número do Passaporte" + "message": "Número do passaporte" }, "licenseNumber": { - "message": "Número da Licença" + "message": "Número da licença" }, "email": { "message": "E-mail" @@ -220,19 +223,19 @@ "message": "Importar" }, "confirmSshKeyPassword": { - "message": "Confirmar senha" + "message": "Confirme a senha" }, "enterSshKeyPasswordDesc": { - "message": "Digite a senha para a chave SSH." + "message": "Digite a senha da chave SSH." }, "enterSshKeyPassword": { - "message": "Digite a sua senha" + "message": "Digite a senha" }, "sshAgentUnlockRequired": { "message": "Desbloqueie seu cofre para aprovar a solicitação de chave SSH." }, "sshAgentUnlockTimeout": { - "message": "Solicitação de chave SSH expirada." + "message": "A solicitação da chave SSH expirou." }, "enableSshAgent": { "message": "Ativar agente SSH" @@ -262,10 +265,10 @@ "message": "Lembrar até que o cofre seja bloqueado" }, "premiumRequired": { - "message": "Requer Assinatura Premium" + "message": "Requer Premium" }, "premiumRequiredDesc": { - "message": "Uma conta premium é necessária para usar esse recurso." + "message": "Uma assinatura Premium é necessária para usar esse recurso." }, "errorOccurred": { "message": "Ocorreu um erro." @@ -277,10 +280,10 @@ "message": "Erro ao descriptografar" }, "couldNotDecryptVaultItemsBelow": { - "message": "O Bitwarden não pode descriptografar o(s) item(ns) do cofre listados abaixo." + "message": "O Bitwarden não pôde descriptografar o(s) item(ns) listados abaixo do cofre." }, "contactCSToAvoidDataLossPart1": { - "message": "Contatar sucesso do cliente", + "message": "Contate o sucesso do consumidor", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -324,7 +327,7 @@ "message": "Dezembro" }, "ex": { - "message": "ex.", + "message": "p. ex.", "description": "Short abbreviation for 'example'." }, "title": { @@ -382,10 +385,10 @@ "message": "Endereço 3" }, "cityTown": { - "message": "Cidade / Localidade" + "message": "Cidade ou localidade" }, "stateProvince": { - "message": "Estado / Província" + "message": "Estado ou província" }, "zipPostalCode": { "message": "CEP / Código postal" @@ -400,16 +403,16 @@ "message": "Cancelar" }, "delete": { - "message": "Excluir" + "message": "Apagar" }, "favorite": { - "message": "Favorito" + "message": "Favoritar" }, "edit": { "message": "Editar" }, "authenticatorKeyTotp": { - "message": "Chave de autenticação (TOTP)" + "message": "Chave do autenticador (TOTP)" }, "authenticatorKey": { "message": "Chave do autenticador" @@ -467,7 +470,7 @@ "message": "Use campos ocultos para dados sensíveis como senhas" }, "checkBoxHelpText": { - "message": "Use caixas de seleção caso deseje preencher automaticamente as caixas de seleção de um formulário, como um e-mail de lembrete" + "message": "Use caixas de seleção se gostaria de preencher automaticamente a caixa de seleção de um formulário, como um lembrar e-mail" }, "linkedHelpText": { "message": "Use um campo vinculado quando você estiver experienciando problemas de preenchimento automático em um site específico." @@ -485,7 +488,7 @@ "message": "Valor" }, "dragToSort": { - "message": "Arrastar para ordenar" + "message": "Arraste para ordenar" }, "cfTypeText": { "message": "Texto" @@ -511,7 +514,7 @@ "message": "Remover" }, "nameRequired": { - "message": "Requer o nome." + "message": "O nome é necessário." }, "addedItem": { "message": "Item adicionado" @@ -526,7 +529,7 @@ "message": "Apagar pasta" }, "deleteAttachment": { - "message": "Excluir Anexo" + "message": "Apagar anexo" }, "deleteItemConfirmation": { "message": "Você realmente deseja enviar para a lixeira?" @@ -544,7 +547,7 @@ "message": "Tem certeza que quer substituir o nome de usuário atual?" }, "noneFolder": { - "message": "Nenhuma Pasta", + "message": "Sem pasta", "description": "This is the folder for uncategorized items" }, "addFolder": { @@ -554,19 +557,19 @@ "message": "Editar pasta" }, "regeneratePassword": { - "message": "Gerar nova senha" + "message": "Regerar senha" }, "copyPassword": { - "message": "Copiar Senha" + "message": "Copiar senha" }, "regenerateSshKey": { "message": "Regerar chave SSH" }, "copySshPrivateKey": { - "message": "Copiar chave SSH privada" + "message": "Copiar chave privada da chave SSH" }, "copyPassphrase": { - "message": "Copiar senha", + "message": "Copiar frase secreta", "description": "Copy passphrase to clipboard" }, "copyUri": { @@ -649,7 +652,7 @@ "message": "Separador de palavras" }, "capitalize": { - "message": "Iniciais em Maiúsculas", + "message": "Iniciais maiúsculas", "description": "Make the first letter of a word uppercase." }, "includeNumber": { @@ -674,7 +677,7 @@ "description": "Label for the avoid ambiguous characters checkbox." }, "generatorPolicyInEffect": { - "message": "Os requisitos de política empresarial foram aplicados às suas opções de gerador.", + "message": "Os requisitos da política empresarial foram aplicados às opções do seu gerador.", "description": "Indicates that a policy limits the credential generator screen." }, "searchCollection": { @@ -697,7 +700,7 @@ "message": "Anexo apagado" }, "deleteAttachmentConfirmation": { - "message": "Tem certeza que deseja excluir esse anexo?" + "message": "Tem certeza que quer apagar esse anexo?" }, "attachmentSaved": { "message": "Anexo salvo" @@ -706,7 +709,7 @@ "message": "Adicionar anexo" }, "maxFileSizeSansPunctuation": { - "message": "Tamanho máximo do arquivo é 500 MB" + "message": "O tamanho máximo do arquivo é de 500 MB" }, "file": { "message": "Arquivo" @@ -721,19 +724,19 @@ "message": "A criptografia legada não é mais suportada. Entre em contato com o suporte para recuperar a sua conta." }, "editedFolder": { - "message": "Pasta editada" + "message": "Pasta salva" }, "addedFolder": { "message": "Pasta adicionada" }, "deleteFolderConfirmation": { - "message": "Você tem certeza que deseja excluir esta pasta?" + "message": "Tem certeza que deseja apagar esta pasta?" }, "deletedFolder": { "message": "Pasta apagada" }, "loginOrCreateNewAccount": { - "message": "Inicie a sessão ou crie uma nova conta para acessar seu cofre seguro." + "message": "Conecte-se ou crie uma nova conta para acessar seu cofre seguro." }, "createAccount": { "message": "Criar conta" @@ -742,19 +745,19 @@ "message": "Novo no Bitwarden?" }, "setAStrongPassword": { - "message": "Defina uma senha forte" + "message": "Configure uma senha forte" }, "finishCreatingYourAccountBySettingAPassword": { - "message": "Termine de criar a sua conta definindo uma senha" + "message": "Termine de criar a sua conta configurando uma senha" }, "logIn": { - "message": "Iniciar sessão" + "message": "Conectar-se" }, "logInToBitwarden": { - "message": "Entre no Bitwarden" + "message": "Conecte-se ao Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Digite o código enviado por e-mail" + "message": "Digite o código enviado ao seu e-mail" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Digite o código do seu aplicativo autenticador" @@ -763,34 +766,34 @@ "message": "Pressione sua YubiKey para autenticar-se" }, "logInWithPasskey": { - "message": "Entrar com chave de acesso" + "message": "Conectar-se com chave de acesso" }, "loginWithDevice": { - "message": "Entrar com dispositivo" + "message": "Conectar-se com dispositivo" }, "useSingleSignOn": { "message": "Usar autenticação única" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A sua organização requer o uso da autenticação única." }, "submit": { "message": "Enviar" }, "masterPass": { - "message": "Senha mestra" + "message": "Senha principal" }, "masterPassDesc": { - "message": "A senha mestra é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha mestra. Não há maneira de recuperar a senha caso você se esqueça." + "message": "A senha principal é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha principal. Não há maneira de recuperar a senha caso você se esqueça." }, "masterPassHintDesc": { - "message": "Uma dica de senha mestra pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." + "message": "Uma dica para a senha principal pode ajudá-lo(a) a lembrá-la caso você esqueça." }, "reTypeMasterPass": { - "message": "Digite novamente a senha mestra" + "message": "Digite novamente a senha principal" }, "masterPassHint": { - "message": "Dica da senha mestra (opcional)" + "message": "Dica da senha principal (opcional)" }, "masterPassHintText": { "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", @@ -806,16 +809,16 @@ } }, "masterPassword": { - "message": "Senha mestre" + "message": "Senha principal" }, "masterPassImportant": { - "message": "Sua senha mestre não pode ser recuperada se você a esquecer!" + "message": "Sua senha principal não pode ser recuperada se você esquecê-la!" }, "confirmMasterPassword": { - "message": "Confirme a senha mestre" + "message": "Confirme a senha principal" }, "masterPassHintLabel": { - "message": "Dica da senha mestre" + "message": "Dica da senha principal" }, "passwordStrengthScore": { "message": "Pontuação de força da senha $SCORE$", @@ -839,7 +842,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se à esta organização definindo uma senha mestra." + "message": "Termine de juntar-se à esta organização configurando uma senha principal." }, "settings": { "message": "Configurações" @@ -851,13 +854,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha mestre" + "message": "Dica da senha principal" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha mestra" + "message": "Receber dica da senha principal" }, "emailRequired": { "message": "O endereço de e-mail é obrigatório." @@ -866,13 +869,13 @@ "message": "Endereço de e-mail inválido." }, "masterPasswordRequired": { - "message": "A senha mestre é obrigatória." + "message": "A senha principal é obrigatória." }, "confirmMasterPasswordRequired": { - "message": "É necessário redigitar a senha mestre." + "message": "É necessário redigitar a senha principal." }, "masterPasswordMinlength": { - "message": "A senha mestre deve ter pelo menos $VALUE$ caracteres.", + "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -882,25 +885,25 @@ } }, "youSuccessfullyLoggedIn": { - "message": "Você entrou na sua conta com sucesso" + "message": "Você conectou-se à sua conta com sucesso" }, "youMayCloseThisWindow": { "message": "Você pode fechar esta janela" }, "masterPassDoesntMatch": { - "message": "A confirmação da senha mestra não corresponde." + "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { - "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." + "message": "A sua nova conta foi criada! Agora você pode se conectar." }, "newAccountCreated2": { "message": "Sua nova conta foi criada!" }, "youHaveBeenLoggedIn": { - "message": "Você entrou!" + "message": "Você foi conectado!" }, "masterPassSent": { - "message": "Enviamos um e-mail com a dica da sua senha mestra." + "message": "Enviamos um e-mail com a dica da sua senha principal." }, "unexpectedError": { "message": "Ocorreu um erro inesperado." @@ -927,7 +930,7 @@ "message": "Confirme a sua identidade para continuar." }, "verificationCodeRequired": { - "message": "Requer o código de verificação." + "message": "O código de verificação é necessário." }, "webauthnCancelOrTimeout": { "message": "A autenticação foi cancelada ou demorou muito. Tente novamente." @@ -961,7 +964,7 @@ "message": "Use seu código de recuperação" }, "insertU2f": { - "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ele tiver um botão, toque nele." + "message": "Insira a sua chave de segurança na porta USB do seu computador. Se ela tiver um botão, toque nele." }, "recoveryCodeTitle": { "message": "Código de recuperação" @@ -1012,10 +1015,10 @@ "message": "Autenticação indisponível" }, "noTwoStepProviders": { - "message": "Esta conta tem a autenticação por duas etapas ativada, no entanto, nenhum dos provedores de autenticação em duas etapas configurados são suportados por este dispositivo." + "message": "Esta conta tem a autenticação em duas etapas ativada, no entanto, nenhum dos provedores de autenticação em duas etapas configurados são suportados por este dispositivo." }, "noTwoStepProviders2": { - "message": "Por favor inclua provedores adicionais que são melhor suportados entre dispositivos (como um aplicativo de autenticação)." + "message": "Adicione provedores adicionais que são melhor suportados entre dispositivos (como um aplicativo autenticador)." }, "twoStepOptions": { "message": "Opções de autenticação em duas etapas" @@ -1030,16 +1033,19 @@ "message": "Especifique a URL de base da sua instalação local do Bitwarden. Exemplo: https://bitwarden.company.com" }, "selfHostedCustomEnvHeader": { - "message": "Para usuários avançados. Você pode especificar a URL de base de cada serviço independentemente." + "message": "Para configuração avançada, você pode especificar a URL de base de cada serviço independentemente." }, "selfHostedEnvFormInvalid": { - "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." + "message": "Você deve adicionar um URL de base de um servidor ou pelo menos um ambiente personalizado." + }, + "selfHostedEnvMustUseHttps": { + "message": "URLs devem usar HTTPS." }, "customEnvironment": { "message": "Ambiente personalizado" }, "baseUrl": { - "message": "URL do Servidor" + "message": "URL do servidor" }, "authenticationTimeout": { "message": "Tempo de autenticação esgotado" @@ -1082,10 +1088,10 @@ "message": "Localização" }, "overwritePassword": { - "message": "Sobrescrever senha" + "message": "Substituir senha" }, "learnMore": { - "message": "Saber mais" + "message": "Saiba mais" }, "featureUnavailable": { "message": "Recurso indisponível" @@ -1115,7 +1121,7 @@ "message": "Você tem certeza que deseja sair?" }, "logOut": { - "message": "Encerrar a Sessão" + "message": "Sair" }, "addNewLogin": { "message": "Nova credencial" @@ -1136,7 +1142,7 @@ "message": "Bloquear cofre" }, "passwordGenerator": { - "message": "Gerador de senha" + "message": "Gerador de senhas" }, "contactUs": { "message": "Contate-nos" @@ -1148,7 +1154,7 @@ "message": "Receber ajuda" }, "fileBugReport": { - "message": "Reportar um bug" + "message": "Relatar um bug" }, "blog": { "message": "Blog" @@ -1160,24 +1166,24 @@ "message": "Sincronizar cofre" }, "changeMasterPass": { - "message": "Alterar senha mestra" + "message": "Alterar senha principal" }, "continueToWebApp": { - "message": "Continuar no app web?" + "message": "Continuar no aplicativo web?" }, "changeMasterPasswordOnWebConfirmation": { - "message": "Você pode alterar a sua senha mestre no app web Bitwarden." + "message": "Você pode alterar a sua senha principal no aplicativo web Bitwarden." }, "fingerprintPhrase": { "message": "Frase biométrica", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "yourAccountsFingerprint": { - "message": "A sua frase biométrica", + "message": "A frase biométrica da sua conta", "description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their public key with another user, for the purposes of sharing." }, "goToWebVault": { - "message": "Ir para o Cofre Web" + "message": "Ir para o cofre web" }, "getMobileApp": { "message": "Baixar o app para celular" @@ -1186,13 +1192,13 @@ "message": "Baixar a extensão para navegador" }, "syncingComplete": { - "message": "Sincronização completada" + "message": "Sincronização concluída" }, "syncingFailed": { - "message": "A sincronização falhou" + "message": "Falha na sincronização" }, "yourVaultIsLocked": { - "message": "Seu cofre está trancado. Verifique sua identidade para continuar." + "message": "O seu cofre está bloqueado. Verifique a sua identidade para continuar." }, "yourAccountIsLocked": { "message": "Sua conta está bloqueada" @@ -1204,13 +1210,13 @@ "message": "Desbloquear com biometria" }, "unlockWithMasterPassword": { - "message": "Desbloquear com senha mestre" + "message": "Desbloquear com senha principal" }, "unlock": { "message": "Desbloquear" }, "loggedInAsOn": { - "message": "Entrou como $EMAIL$ em $HOSTNAME$.", + "message": "Conectado como $EMAIL$ em $HOSTNAME$.", "placeholders": { "email": { "content": "$1", @@ -1223,10 +1229,10 @@ } }, "invalidMasterPassword": { - "message": "Senha mestra inválida" + "message": "Senha principal inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Senha mestre inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", + "message": "Senha principal inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1235,7 +1241,7 @@ } }, "twoStepLoginConfirmation": { - "message": "A autenticação em duas etapas torna sua conta mais segura, exigindo que você verifique o seu login com outro dispositivo, como uma chave de segurança, um aplicativo autenticador, SMS, chamada telefônica ou e-mail. A autenticação em duas etapas pode ser ativada no cofre web em bitwarden.com. Você deseja visitar o site agora?" + "message": "A autenticação em duas etapas torna sua conta mais segura, exigindo que você verifique a sua autenticação com outro dispositivo, como uma chave de segurança, um aplicativo autenticador, SMS, chamada telefônica ou e-mail. A autenticação em duas etapas pode ser ativada no cofre web em bitwarden.com. Você deseja visitar o site agora?" }, "twoStepLogin": { "message": "Autenticação em duas etapas" @@ -1253,7 +1259,7 @@ "message": "Ação do tempo limite" }, "vaultTimeoutDesc": { - "message": "Escolha quando o tempo limite do seu cofre irá se esgotar e execute a ação selecionada." + "message": "Escolha quando o seu cofre executará a ação do tempo limite do cofre." }, "immediately": { "message": "Imediatamente" @@ -1295,7 +1301,7 @@ "message": "Quando o sistema hibernar" }, "onLocked": { - "message": "Quando o sistema estiver bloqueado" + "message": "Quando o sistema for bloqueado" }, "onRestart": { "message": "Ao reiniciar" @@ -1321,25 +1327,25 @@ "message": "Minimizar para ícone da bandeja" }, "enableMinToTrayDesc": { - "message": "Ao minimizar a janela, mostra um ícone na bandeja do sistema." + "message": "Ao minimizar a janela, mostrar um ícone na bandeja do sistema." }, "enableMinToMenuBar": { "message": "Minimizar para a barra de menu" }, "enableMinToMenuBarDesc": { - "message": "Ao minimizar a janela, mostra um ícone na barra de menus." + "message": "Ao minimizar a janela, mostrar um ícone na barra de menu." }, "enableCloseToTray": { "message": "Fechar para ícone da bandeja" }, "enableCloseToTrayDesc": { - "message": "Ao fechar a janela, mostra um ícone na bandeja do sistema." + "message": "Ao fechar a janela, mostrar um ícone na bandeja do sistema." }, "enableCloseToMenuBar": { "message": "Fechar para barra de menu" }, "enableCloseToMenuBarDesc": { - "message": "Ao fechar a janela, mostra um ícone na barra de menu." + "message": "Ao fechar a janela, mostrar um ícone na barra de menu." }, "enableTray": { "message": "Ativar icone da bandeja" @@ -1348,19 +1354,19 @@ "message": "Sempre mostrar um ícone na bandeja do sistema." }, "startToTray": { - "message": "Iniciar no ícone da bandeja" + "message": "Abrir no ícone da bandeja" }, "startToTrayDesc": { - "message": "Quando o aplicativo for iniciado, apenas mostrar um ícone na bandeja do sistema." + "message": "Ao abrir o aplicativo, apenas mostrar um ícone na bandeja do sistema." }, "startToMenuBar": { - "message": "Iniciar na barra de menu" + "message": "Abrir na barra de menu" }, "startToMenuBarDesc": { - "message": "Quando o aplicativo for iniciado, apenas mostrar um ícone na barra de menu." + "message": "Ao abrir o aplicativo, apenas mostrar um ícone na barra de menu." }, "openAtLogin": { - "message": "Iniciar automaticamente ao iniciar sessão" + "message": "Iniciar automaticamente com o usuário" }, "openAtLoginDesc": { "message": "Inicie o aplicativo Bitwarden Desktop automaticamente junto com o usuário." @@ -1369,10 +1375,10 @@ "message": "Exibir sempre na Dock" }, "alwaysShowDockDesc": { - "message": "Mostrar o ícone do Bitwarden na Dock, mesmo quando minimizado para a barra de menu." + "message": "Mostrar o ícone do Bitwarden na Dock, mesmo quando minimizado na barra de menu." }, "confirmTrayTitle": { - "message": "Confirmar ocultamento da bandeja" + "message": "Confirmar ocultar da bandeja" }, "confirmTrayDesc": { "message": "Desativar esta configuração também desativará todas as outras configurações relacionadas à bandeja." @@ -1414,7 +1420,7 @@ } }, "restartToUpdate": { - "message": "Reinicie para atualizar" + "message": "Reiniciar para atualizar" }, "restartToUpdateDesc": { "message": "A versão $VERSION_NUM$ está pronta para ser instalada. Você deve reiniciar o Bitwarden para completar a instalação. Você quer reiniciar e atualizar agora?", @@ -1435,7 +1441,7 @@ "message": "Reiniciar" }, "later": { - "message": "Mais Tarde" + "message": "Mais tarde" }, "noUpdatesAvailable": { "message": "Não há atualizações disponíveis no momento. Você está usando a versão mais recente." @@ -1470,25 +1476,25 @@ "message": "Gerenciar assinatura" }, "premiumManageAlert": { - "message": "Você pode gerenciar a sua assinatura premium no cofre web em bitwarden.com. Você deseja visitar o site agora?" + "message": "Você pode gerenciar a sua assinatura no cofre web do bitwarden.com. Você deseja visitar o site agora?" }, "premiumRefresh": { "message": "Recarregar assinatura" }, "premiumNotCurrentMember": { - "message": "Você não possui uma assinatura Premium." + "message": "Você não é um membro Premium atualmente." }, "premiumSignUpAndGet": { "message": "Inscreva-se para uma assinatura Premium e receba:" }, "premiumSignUpStorage": { - "message": "1 GB de armazenamento de arquivos encriptados." + "message": "1 GB de armazenamento criptografado para anexos de arquivos." }, "premiumSignUpTwoStepOptions": { - "message": "Opções de autenticação em duas etapas como YubiKey e Duo." + "message": "Opções proprietárias de autenticação em duas etapas como YubiKey e Duo." }, "premiumSignUpReports": { - "message": "Higiene de senha, saúde da conta, e relatórios sobre violação de dados para manter o seu cofre seguro." + "message": "Higiene de senha, saúde da conta, e relatórios de brechas de dados para manter o seu cofre seguro." }, "premiumSignUpTotp": { "message": "Gerador de códigos de verificação TOTP (2FA) para credenciais no seu cofre." @@ -1497,22 +1503,22 @@ "message": "Prioridade no suporte ao cliente." }, "premiumSignUpFuture": { - "message": "Todos os recursos premium no futuro. Mais em breve!" + "message": "Todos os recursos Premium no futuro. Mais em breve!" }, "premiumPurchase": { "message": "Comprar Premium" }, "premiumPurchaseAlertV2": { - "message": "Você pode comprar Premium nas configurações de sua conta no aplicativo web do Bitwarden." + "message": "Você pode comprar o Premium nas configurações da sua conta no aplicativo web do Bitwarden." }, "premiumCurrentMember": { - "message": "Você é um membro premium!" + "message": "Você é um membro Premium!" }, "premiumCurrentMemberThanks": { "message": "Obrigado por apoiar o Bitwarden." }, "premiumPrice": { - "message": "Tudo por apenas $PRICE$ /ano!", + "message": "Tudo por apenas $PRICE$ por ano!", "placeholders": { "price": { "content": "$1", @@ -1521,7 +1527,7 @@ } }, "refreshComplete": { - "message": "Atualização completada" + "message": "Recarregamento concluído" }, "passwordHistory": { "message": "Histórico de senhas" @@ -1604,16 +1610,16 @@ "message": "Serviços" }, "hideBitwarden": { - "message": "Ocultar o Bitwarden" + "message": "Ocultar Bitwarden" }, "hideOthers": { "message": "Ocultar outros" }, "showAll": { - "message": "Mostrar todos" + "message": "Mostrar tudo" }, "quitBitwarden": { - "message": "Sair do Bitwarden" + "message": "Fechar Bitwarden" }, "valueCopied": { "message": "$VALUE$ copiado(a)", @@ -1632,7 +1638,7 @@ "message": "Erro ao acessar token de recarregamento" }, "errorRefreshingAccessTokenDesc": { - "message": "Nenhum token de atualização ou chave de API foi encontrado. Tente sair e entrar novamente." + "message": "Nenhum token de recarregamento ou chave de API foi encontrado. Tente desconectar-se e conectar-se novamente." }, "help": { "message": "Ajuda" @@ -1641,7 +1647,7 @@ "message": "Janela" }, "checkPassword": { - "message": "Verifique se a senha foi exposta." + "message": "Confira se a senha foi exposta." }, "passwordExposed": { "message": "Esta senha foi exposta $VALUE$ vez(es) em brechas de dados. Você deve alterá-la.", @@ -1653,7 +1659,7 @@ } }, "passwordSafe": { - "message": "Esta senha não foi encontrada em brechas de dados conhecidas. Deve ser seguro de usar." + "message": "Esta senha não foi encontrada em brechas de dados conhecidas. Deve ser segura de usar." }, "baseDomain": { "message": "Domínio de base", @@ -1740,7 +1746,7 @@ "message": "Esta senha será usada para exportar e importar este arquivo" }, "accountRestrictedOptionDescription": { - "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha mestre da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." + "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtected": { "message": "Protegido por senha" @@ -1807,10 +1813,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Senha mestra fraca" + "message": "Senha principal fraca" }, "weakMasterPasswordDesc": { - "message": "A senha mestra que você selecionou está fraca. Você deve usar uma senha mestra forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha mestra?" + "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" }, "pin": { "message": "PIN", @@ -1853,13 +1859,13 @@ "message": "Pedir pelo Touch ID ao iniciar" }, "lockWithMasterPassOnRestart1": { - "message": "Bloquear com senha mestra ao reiniciar" + "message": "Bloquear com senha principal ao reiniciar" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Exigir senha mestra ou PIN ao reiniciar o app" + "message": "Exigir senha principal ou PIN ao reiniciar o app" }, "requireMasterPasswordOnAppRestart": { - "message": "Exigir senha mestra ao reiniciar o app" + "message": "Exigir senha principal ao reiniciar o app" }, "deleteAccount": { "message": "Apagar conta" @@ -1932,7 +1938,7 @@ "message": "Ação do tempo limite do cofre" }, "vaultTimeoutActionLockDesc": { - "message": "A senha mestre ou outro método de desbloqueio é necessário para acessar seu cofre novamente." + "message": "A senha principal ou outro método de desbloqueio é necessário para acessar seu cofre novamente." }, "vaultTimeoutActionLogOutDesc": { "message": "Reautenticação é necessária para acessar seu cofre novamente." @@ -1976,14 +1982,14 @@ "message": "Autenticação única empresarial" }, "setMasterPassword": { - "message": "Configurar senha mestre" + "message": "Configurar senha principal" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha mestre.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha mestre.", + "message": "Sua organização requer que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -2052,16 +2058,16 @@ "description": "Default title for the user verification dialog." }, "currentMasterPass": { - "message": "Senha mestre atual" + "message": "Senha principal atual" }, "newMasterPass": { - "message": "Nova senha mestra" + "message": "Nova senha principal" }, "confirmNewMasterPass": { - "message": "Confirmar nova senha mestra" + "message": "Confirmar nova senha principal" }, "masterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a sua senha mestra cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a sua senha principal cumpra aos seguintes requisitos:" }, "policyInEffectMinComplexity": { "message": "Pontuação mínima de complexidade de $SCORE$", @@ -2100,7 +2106,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "A sua nova senha mestra não cumpre aos requisitos da política." + "message": "A sua nova senha principal não cumpre aos requisitos da política." }, "receiveMarketingEmailsV2": { "message": "Receba conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." @@ -2410,40 +2416,40 @@ "message": "Você precisa verificar o seu e-mail para usar este recurso." }, "passwordPrompt": { - "message": "Solicitação nova de senha mestra" + "message": "Solicitação nova de senha principal" }, "passwordConfirmation": { - "message": "Confirmação de senha mestra" + "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha mestra para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." }, "masterPasswordSuccessfullySet": { - "message": "Senha mestra definida com sucesso" + "message": "Senha principal definida com sucesso" }, "updatedMasterPassword": { - "message": "Senha mestra atualizada" + "message": "Senha principal atualizada" }, "updateMasterPassword": { - "message": "Atualizar senha mestre" + "message": "Atualizar senha principal" }, "updateMasterPasswordWarning": { - "message": "Sua senha mestre foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha mestre não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestre agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "changePasswordWarning": { "message": "Após mudar a sua senha, será necessário entrar novamente com a sua nova senha. Sessões ativas em outros dispositivos serão encerradas em até uma hora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Mude a sua senha mestre para completar a recuperação de conta." + "message": "Mude a sua senha principal para completar a recuperação de conta." }, "updateMasterPasswordSubtitle": { - "message": "Sua senha mestre não corresponde aos requisitos da organização. Mude a sua senha mestre para continuar." + "message": "Sua senha principal não corresponde aos requisitos da organização. Mude a sua senha principal para continuar." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha mestra para acessar o seu cofre." + "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha principal para acessar o seu cofre." }, "tryAgain": { "message": "Repetir" @@ -2467,7 +2473,7 @@ "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha mestra" + "message": "Usar a senha principal" }, "usePin": { "message": "Usar PIN" @@ -2568,7 +2574,7 @@ "message": "Inscrição automática" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha mestra." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "vaultExportDisabled": { "message": "Exportação de cofre removida" @@ -2580,13 +2586,13 @@ "message": "Adicionar conta" }, "removeMasterPassword": { - "message": "Remover senha mestre" + "message": "Remover senha principal" }, "removedMasterPassword": { - "message": "Senha mestre removida" + "message": "Senha principal removida" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha mestra não é mais necessária para membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." }, "organizationName": { "message": "Nome da organização" @@ -2991,7 +2997,7 @@ "message": "Cofre" }, "loginWithMasterPassword": { - "message": "Entrar com senha mestre" + "message": "Entrar com senha principal" }, "rememberEmail": { "message": "Lembrar e-mail" @@ -3183,13 +3189,13 @@ "message": "para editar o seu endereço de e-mail." }, "exposedMasterPassword": { - "message": "Senha mestre comprometida" + "message": "Senha principal comprometida" }, "exposedMasterPasswordDesc": { "message": "Senha encontrada em um vazamento de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha já exposta?" }, "weakAndExposedMasterPassword": { - "message": "Senha mestra fraca e comprometida" + "message": "Senha principal fraca e comprometida" }, "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" @@ -3213,7 +3219,7 @@ "message": "Você foi desconectado porque seu token de recarregamento não pôde ser recuperado. Por favor, entre novamente para resolver esse problema." }, "masterPasswordHint": { - "message": "A sua senha mestra não pode ser recuperada se você esquecê-la!" + "message": "A sua senha principal não pode ser recuperada se você esquecê-la!" }, "characterMinimum": { "message": "Mínimo de $LENGTH$ caracteres", @@ -3225,7 +3231,7 @@ } }, "windowsBiometricUpdateWarning": { - "message": "O Bitwarden recomenda a atualização das suas configurações de biometria para exigir a sua senha mestra (ou PIN) no primeiro desbloqueio. Gostaria de atualizar suas configurações agora?" + "message": "O Bitwarden recomenda a atualização das suas configurações de biometria para exigir a sua senha principal (ou PIN) no primeiro desbloqueio. Gostaria de atualizar suas configurações agora?" }, "windowsBiometricUpdateWarningTitle": { "message": "Atualização recomendada das configurações" @@ -3613,7 +3619,7 @@ "message": "Código" }, "lastPassMasterPassword": { - "message": "Senha mestre do LastPass" + "message": "Senha mestra do LastPass" }, "lastPassAuthRequired": { "message": "Autenticação do LastPass necessária" @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Número do cartão" + }, + "upgradeNow": { + "message": "Faça upgrade agora" + }, + "builtInAuthenticator": { + "message": "Autenticador integrado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de arquivos" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitoramento de brechas" + }, + "andMoreFeatures": { + "message": "E mais!" + }, + "planDescPremium": { + "message": "Segurança on-line completa" + }, + "upgradeToPremium": { + "message": "Faça upgrade para o Premium" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index 5fdf0e85cdc..d19c0075873 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Bem-vindo de volta" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "Os URLs devem usar HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Número do cartão" + }, + "upgradeNow": { + "message": "Atualizar agora" + }, + "builtInAuthenticator": { + "message": "Autenticador incorporado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de ficheiros" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitorização de violações" + }, + "andMoreFeatures": { + "message": "E muito mais!" + }, + "planDescPremium": { + "message": "Segurança total online" + }, + "upgradeToPremium": { + "message": "Atualizar para o Premium" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index 5a6f4b0276e..fbb1d018b60 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mediu personalizat" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 63837c98e5a..0170bd57310 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "У вас нет разрешения на редактирование этого элемента" + }, "welcomeBack": { "message": "С возвращением" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." }, + "selfHostedEnvMustUseHttps": { + "message": "URL должны использовать HTTPS." + }, "customEnvironment": { "message": "Пользовательское окружение" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Номер карты" + }, + "upgradeNow": { + "message": "Изменить сейчас" + }, + "builtInAuthenticator": { + "message": "Встроенный аутентификатор" + }, + "secureFileStorage": { + "message": "Защищенное хранилище файлов" + }, + "emergencyAccess": { + "message": "Экстренный доступ" + }, + "breachMonitoring": { + "message": "Мониторинг нарушений" + }, + "andMoreFeatures": { + "message": "И многое другое!" + }, + "planDescPremium": { + "message": "Полная онлайн-защищенность" + }, + "upgradeToPremium": { + "message": "Обновить до Премиум" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 7f191bf2161..4193867ead5 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index aaf45ac65b2..8fecc5de1b9 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Na úpravu tejto položky nemáte oprávnenie" + }, "welcomeBack": { "message": "Vitajte späť" }, @@ -772,7 +775,7 @@ "message": "Použiť jednotné prihlásenie" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Vaša organizácia vyžaduje jednotné prihlasovanie." }, "submit": { "message": "Potvrdiť" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte pridať buď základnú adresu URL servera, alebo aspoň jedno vlastné prostredie." }, + "selfHostedEnvMustUseHttps": { + "message": "Adresy URL musia používať HTTPS." + }, "customEnvironment": { "message": "Vlastné prostredie" }, @@ -1728,7 +1734,7 @@ "message": "Export trezoru" }, "fileFormat": { - "message": "Formát Súboru" + "message": "Formát súboru" }, "fileEncryptedExportWarningDesc": { "message": "Tento exportovaný súbor bude chránený heslom a na dešifrovanie bude potrebné heslo súboru." @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Číslo karty" + }, + "upgradeNow": { + "message": "Upgradovať teraz" + }, + "builtInAuthenticator": { + "message": "Zabudovaný autentifikátor" + }, + "secureFileStorage": { + "message": "Bezpečné ukladanie súborov" + }, + "emergencyAccess": { + "message": "Núdzový prístup" + }, + "breachMonitoring": { + "message": "Sledovanie únikov" + }, + "andMoreFeatures": { + "message": "A ešte viac!" + }, + "planDescPremium": { + "message": "Úplné online zabezpečenie" + }, + "upgradeToPremium": { + "message": "Upgradovať na Prémium" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index c2380687634..329403e43fc 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Okolje po meri" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index 1e94787f824..b7df1855552 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Добродошли назад" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Прилагођено окружење" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index 6811a6b8583..e06f78efbf6 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Du har inte behörighet att redigera detta objekt" + }, "welcomeBack": { "message": "Välkommen tillbaka" }, @@ -772,7 +775,7 @@ "message": "Använd Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Din organisation kräver single sign-on." }, "submit": { "message": "Skicka" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Du måste lägga till antingen basserverns URL eller minst en anpassad miljö." }, + "selfHostedEnvMustUseHttps": { + "message": "Webbadresser måste använda HTTPS." + }, "customEnvironment": { "message": "Anpassad miljö" }, @@ -4186,9 +4192,33 @@ "message": "Arkiverade objekt är uteslutna från allmänna sökresultat och förslag för autofyll. Är du säker på att du vill arkivera detta objekt?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Postnummer" }, "cardNumberLabel": { - "message": "Card number" + "message": "Kortnummer" + }, + "upgradeNow": { + "message": "Uppgradera nu" + }, + "builtInAuthenticator": { + "message": "Inbyggd autenticator" + }, + "secureFileStorage": { + "message": "Säker fillagring" + }, + "emergencyAccess": { + "message": "Nödåtkomst" + }, + "breachMonitoring": { + "message": "Intrångsmonitorering" + }, + "andMoreFeatures": { + "message": "och mer!" + }, + "planDescPremium": { + "message": "Komplett säkerhet online" + }, + "upgradeToPremium": { + "message": "Uppgradera till Premium" } } diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index 5e6bccf2a6e..de574204738 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "மீண்டும் வருக" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "நீங்கள் அடிப்படை சேவையக URL-ஐயாவது அல்லது குறைந்தது ஒரு தனிப்பயன் சூழலையாவது சேர்க்க வேண்டும்." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "தனிப்பயன் சூழல்" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index c6856f3375a..70d4c7cb494 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index e2d4509ddf4..a9b197f946c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 88ee8f4e635..94f64b31811 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" + }, "welcomeBack": { "message": "Tekrar hoş geldiniz" }, @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Temel Sunucu URL’sini veya en az bir özel ortam eklemelisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL'ler HTTPS kullanmalıdır." + }, "customEnvironment": { "message": "Özel ortam" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "Kart numarası" + }, + "upgradeNow": { + "message": "Şimdi yükselt" + }, + "builtInAuthenticator": { + "message": "Dahili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvenli dosya depolama" + }, + "emergencyAccess": { + "message": "Acil durum erişimi" + }, + "breachMonitoring": { + "message": "İhlal izleme" + }, + "andMoreFeatures": { + "message": "Ve daha fazlası!" + }, + "planDescPremium": { + "message": "Eksiksiz çevrimiçi güvenlik" + }, + "upgradeToPremium": { + "message": "Premium'a yükselt" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index 5c4583aa9b6..b6e0b18a981 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "З поверненням" }, @@ -262,10 +265,10 @@ "message": "Пам'ятати до блокування сховища" }, "premiumRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "premiumRequiredDesc": { - "message": "Для використання цієї функції необхідна передплата преміум." + "message": "Для використання цієї функції необхідна передплата Premium." }, "errorOccurred": { "message": "Сталася помилка." @@ -772,7 +775,7 @@ "message": "Використати єдиний вхід" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша організація вимагає єдиний вхід (SSO)." }, "submit": { "message": "Відправити" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-адреси повинні бути HTTPS." + }, "customEnvironment": { "message": "Власне середовище" }, @@ -1226,7 +1232,7 @@ "message": "Неправильний головний пароль" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "Неправильний головний пароль. Перевірте правильність адреси електронної пошти та розміщення облікового запису на $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1315,7 +1321,7 @@ "description": "Clipboard is the operating system thing where you copy/paste data to on your device." }, "showIconsChangePasswordUrls": { - "message": "Show website icons and retrieve change password URLs" + "message": "Показувати піктограми вебсайтів та отримувати адреси для зміни паролів" }, "enableMinToTray": { "message": "Згортати до системного лотка" @@ -1464,22 +1470,22 @@ "message": "номер картки" }, "premiumMembership": { - "message": "Преміум статус" + "message": "Передплата Premium" }, "premiumManage": { "message": "Керувати передплатою" }, "premiumManageAlert": { - "message": "Ви можете керувати своїм статусом у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" + "message": "Ви можете керувати передплатою у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" }, "premiumRefresh": { "message": "Оновити стан передплати" }, "premiumNotCurrentMember": { - "message": "Зараз у вас немає передплати преміум." + "message": "Зараз у вас немає передплати Premium." }, "premiumSignUpAndGet": { - "message": "Передплатіть преміум і отримайте:" + "message": "Передплатіть Premium і отримайте:" }, "premiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." @@ -1497,22 +1503,22 @@ "message": "Пріоритетну технічну підтримку." }, "premiumSignUpFuture": { - "message": "Усі майбутні преміумфункції. Їх буде більше!" + "message": "Усі майбутні функції Premium. Їх буде більше!" }, "premiumPurchase": { - "message": "Придбати преміум" + "message": "Придбати Premium" }, "premiumPurchaseAlertV2": { - "message": "Ви можете придбати Преміум у налаштуваннях облікового запису вебпрограмі Bitwarden." + "message": "Ви можете придбати Premium у налаштуваннях облікового запису вебпрограми Bitwarden." }, "premiumCurrentMember": { - "message": "Ви користуєтеся передплатою преміум!" + "message": "Ви користуєтеся передплатою Premium!" }, "premiumCurrentMemberThanks": { "message": "Дякуємо за підтримку Bitwarden." }, "premiumPrice": { - "message": "Всього лише $PRICE$ / за рік!", + "message": "Лише $PRICE$ / рік!", "placeholders": { "price": { "content": "$1", @@ -1856,10 +1862,10 @@ "message": "Блокувати головним паролем при перезапуску" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "Вимагати головний пароль або PIN після перезапуску програми" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "Вимагати головний пароль після перезапуску програми" }, "deleteAccount": { "message": "Видалити обліковий запис" @@ -2023,7 +2029,7 @@ "message": "Bitwarden може автоматично заповнювати одноразові коди двоетапної перевірки. Відкрийте камеру, щоб сканувати QR-код на цьому вебсайті, або скопіюйте і вставте ключ у це поле." }, "premium": { - "message": "Преміум", + "message": "Premium", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { @@ -2559,7 +2565,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "Minimum custom timeout is 1 minute." + "message": "Мінімальний власний час очікування – 1 хвилина." }, "inviteAccepted": { "message": "Запрошення прийнято" @@ -2676,7 +2682,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$.", "placeholders": { "organization": { "content": "$1", @@ -2685,7 +2691,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи моїх збірок не будуть включені.", "placeholders": { "organization": { "content": "$1", @@ -2976,7 +2982,7 @@ "message": "Ключ API" }, "premiumSubcriptionRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "organizationIsDisabled": { "message": "Організацію призупинено" @@ -3625,10 +3631,10 @@ "message": "Увійдіть з використанням облікових даних вашої компанії." }, "importDirectlyFromBrowser": { - "message": "Import directly from browser" + "message": "Імпортувати безпосередньо з браузера" }, "browserProfile": { - "message": "Browser Profile" + "message": "Профіль браузера" }, "seeDetailedInstructions": { "message": "Перегляньте докладні інструкції на нашому довідковому сайті", @@ -3873,10 +3879,10 @@ "message": "Змінити ризикований пароль" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." + "message": "Цей запис ризикований, і не має адреси вебсайту. Додайте адресу вебсайту і змініть пароль для вдосконалення безпеки." }, "missingWebsite": { - "message": "Missing website" + "message": "Немає вебсайту" }, "cannotRemoveViewOnlyCollections": { "message": "Ви не можете вилучати збірки, маючи дозвіл лише на перегляд: $COLLECTIONS$", @@ -3974,10 +3980,10 @@ "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "aboutThisSetting": { - "message": "About this setting" + "message": "Про ці налаштування" }, "permitCipherDetailsDescription": { - "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." + "message": "Bitwarden використовуватиме збережені URI-адреси записів для визначення піктограм вебсайтів або URL-адрес для зміни паролів, щоб вдосконалити вашу роботу. Під час використання цієї послуги ваша інформація не збирається і не зберігається." }, "assignToCollections": { "message": "Призначити до збірок" @@ -4123,72 +4129,96 @@ "message": "Bitwarden не перевіряє місця введення. Переконайтеся, що у вас відкрите правильне вікно і вибрано потрібне поле, перш ніж застосувати комбінацію клавіш." }, "typeShortcut": { - "message": "Type shortcut" + "message": "Введіть комбінацію клавіш" }, "editAutotypeShortcutDescription": { - "message": "Include one or two of the following modifiers: Ctrl, Alt, Win, or Shift, and a letter." + "message": "Використайте один або два таких модифікацій: Ctrl, Alt, Win, Shift, і літеру." }, "invalidShortcut": { - "message": "Invalid shortcut" + "message": "Недійсна комбінація клавіш" }, "moreBreadcrumbs": { "message": "Інші елементи", "description": "This is used in the context of a breadcrumb navigation, indicating that there are more items in the breadcrumb trail that are not currently displayed." }, "next": { - "message": "Next" + "message": "Далі" }, "confirmKeyConnectorDomain": { - "message": "Confirm Key Connector domain" + "message": "Підтвердити домен Key Connector" }, "confirm": { - "message": "Confirm" + "message": "Підтвердити" }, "enableAutotypeShortcutPreview": { - "message": "Enable autotype shortcut (Feature Preview)" + "message": "Увімкнути комбінацію клавіш автовведення (тестова функція)" }, "enableAutotypeShortcutDescription": { - "message": "Be sure you are in the correct field before using the shortcut to avoid filling data into the wrong place." + "message": "Перед використанням комбінації клавіш виберіть правильне поле, щоб уникнути заповнення даних у невідповідному місці." }, "editShortcut": { - "message": "Edit shortcut" + "message": "Редагувати комбінацію клавіш" }, "archiveNoun": { - "message": "Archive", + "message": "Архів", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Архівувати", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Видобути" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Записи в архіві" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Немає записів у архіві" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Архівовані записи з'являтимуться тут і будуть виключені з результатів звичайного пошуку та пропозицій автозаповнення." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "Запис архівовано" }, "itemWasUnarchived": { - "message": "Item was unarchived" + "message": "Запис розархівовано" }, "archiveItem": { - "message": "Archive item" + "message": "Архівувати запис" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Архівовані записи виключаються з результатів звичайного пошуку та пропозицій автозаповнення. Ви дійсно хочете архівувати цей запис?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Поштовий індекс" }, "cardNumberLabel": { - "message": "Card number" + "message": "Номер картки" + }, + "upgradeNow": { + "message": "Покращити" + }, + "builtInAuthenticator": { + "message": "Вбудований автентифікатор" + }, + "secureFileStorage": { + "message": "Захищене сховище файлів" + }, + "emergencyAccess": { + "message": "Екстрений доступ" + }, + "breachMonitoring": { + "message": "Моніторинг витоків даних" + }, + "andMoreFeatures": { + "message": "Інші можливості!" + }, + "planDescPremium": { + "message": "Повна онлайн-безпека" + }, + "upgradeToPremium": { + "message": "Покращити до Premium" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index dd7747dab9f..db945022070 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Chào mừng bạn trở lại" }, @@ -772,7 +775,7 @@ "message": "Dùng đăng nhập một lần" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tổ chức của bạn yêu cầu đăng nhập một lần." }, "submit": { "message": "Gửi" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "Bạn phải thêm URL máy chủ cơ sở hoặc ít nhất một môi trường tùy chỉnh." }, + "selfHostedEnvMustUseHttps": { + "message": "URL phải sử dụng HTTPS." + }, "customEnvironment": { "message": "Môi trường tùy chỉnh" }, @@ -4186,9 +4192,33 @@ "message": "Các mục đã lưu trữ sẽ bị loại khỏi kết quả tìm kiếm chung và gợi ý tự động điền. Bạn có chắc chắn muốn lưu trữ mục này không?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Mã ZIP / Bưu điện" }, "cardNumberLabel": { - "message": "Card number" + "message": "Số thẻ" + }, + "upgradeNow": { + "message": "Nâng cấp ngay" + }, + "builtInAuthenticator": { + "message": "Trình xác thực tích hợp" + }, + "secureFileStorage": { + "message": "Lưu trữ tệp an toàn" + }, + "emergencyAccess": { + "message": "Truy cập khẩn cấp" + }, + "breachMonitoring": { + "message": "Giám sát vi phạm" + }, + "andMoreFeatures": { + "message": "Và nhiều hơn nữa!" + }, + "planDescPremium": { + "message": "Bảo mật trực tuyến toàn diện" + }, + "upgradeToPremium": { + "message": "Nâng cấp lên gói Cao cấp" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 7a4e9f7bc7b..93c907743d9 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "您没有编辑此项目的权限" + }, "welcomeBack": { "message": "欢迎回来" }, @@ -772,7 +775,7 @@ "message": "使用单点登录" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的组织要求单点登录。" }, "submit": { "message": "提交" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必须使用 HTTPS。" + }, "customEnvironment": { "message": "自定义环境" }, @@ -1898,7 +1904,7 @@ "message": "您必须至少选择一个集合。" }, "premiumUpdated": { - "message": "您已升级到高级会员。" + "message": "您已升级为高级版。" }, "restore": { "message": "恢复" @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "卡号" + }, + "upgradeNow": { + "message": "立即升级" + }, + "builtInAuthenticator": { + "message": "内置身份验证器" + }, + "secureFileStorage": { + "message": "安全文件存储" + }, + "emergencyAccess": { + "message": "紧急访问" + }, + "breachMonitoring": { + "message": "数据泄露监测" + }, + "andMoreFeatures": { + "message": "以及更多!" + }, + "planDescPremium": { + "message": "全面的在线安全防护" + }, + "upgradeToPremium": { + "message": "升级为高级版" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 78a4f950f40..0dc4d0911fc 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "歡迎回來" }, @@ -772,7 +775,7 @@ "message": "使用單一登入" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的組織要求使用單一登入。" }, "submit": { "message": "送出" @@ -1035,6 +1038,9 @@ "selfHostedEnvFormInvalid": { "message": "您必須新增伺服器網域 URL 或至少一個自訂環境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必須使用 HTTPS。" + }, "customEnvironment": { "message": "自訂環境" }, @@ -4190,5 +4196,29 @@ }, "cardNumberLabel": { "message": "支付卡號碼" + }, + "upgradeNow": { + "message": "立即升級" + }, + "builtInAuthenticator": { + "message": "內建驗證器" + }, + "secureFileStorage": { + "message": "安全檔案儲存" + }, + "emergencyAccess": { + "message": "緊急存取" + }, + "breachMonitoring": { + "message": "外洩監控" + }, + "andMoreFeatures": { + "message": "以及其他功能功能!" + }, + "planDescPremium": { + "message": "完整的線上安全" + }, + "upgradeToPremium": { + "message": "升級到 Premium" } } diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 88be6ebd4f5..512f8c638ef 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.10.2", + "version": "2025.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.10.2", + "version": "2025.11.0", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index d122978f943..a24bd703248 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.10.2", + "version": "2025.11.0", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index b7005872f25..55092788079 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -1,6 +1,6 @@
    -
    {{ "sshkeyApprovalTitle" | i18n }}
    +
    {{ "sshkeyApprovalTitle" | i18n }}
    { let service: DesktopPremiumUpgradePromptService; let messager: MockProxy; + let configService: MockProxy; + let dialogService: MockProxy; beforeEach(async () => { messager = mock(); + configService = mock(); + dialogService = mock(); + await TestBed.configureTestingModule({ providers: [ DesktopPremiumUpgradePromptService, { provide: MessagingService, useValue: messager }, + { provide: ConfigService, useValue: configService }, + { provide: DialogService, useValue: dialogService }, ], }).compileComponents(); @@ -22,9 +33,38 @@ describe("DesktopPremiumUpgradePromptService", () => { }); describe("promptForPremium", () => { - it("navigates to the premium update screen", async () => { + let openSpy: jest.SpyInstance; + + beforeEach(() => { + openSpy = jest.spyOn(PremiumUpgradeDialogComponent, "open").mockImplementation(); + }); + + afterEach(() => { + openSpy.mockRestore(); + }); + + it("opens the new premium upgrade dialog when feature flag is enabled", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + await service.promptForPremium(); + + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); + expect(openSpy).toHaveBeenCalledWith(dialogService); + expect(messager.send).not.toHaveBeenCalled(); + }); + + it("sends openPremium message when feature flag is disabled", async () => { + configService.getFeatureFlag.mockResolvedValue(false); + + await service.promptForPremium(); + + expect(configService.getFeatureFlag).toHaveBeenCalledWith( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); expect(messager.send).toHaveBeenCalledWith("openPremium"); + expect(openSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts index f2375ecfebb..5004e5ed547 100644 --- a/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts +++ b/apps/desktop/src/services/desktop-premium-upgrade-prompt.service.ts @@ -1,15 +1,29 @@ import { inject } from "@angular/core"; +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { DialogService } from "@bitwarden/components"; /** * This class handles the premium upgrade process for the desktop. */ export class DesktopPremiumUpgradePromptService implements PremiumUpgradePromptService { private messagingService = inject(MessagingService); + private configService = inject(ConfigService); + private dialogService = inject(DialogService); async promptForPremium() { - this.messagingService.send("openPremium"); + const showNewDialog = await this.configService.getFeatureFlag( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); + + if (showNewDialog) { + PremiumUpgradeDialogComponent.open(this.dialogService); + } else { + this.messagingService.send("openPremium"); + } } } diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index a0a3967f468..62aa9fb1ce2 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -19,6 +19,9 @@ export enum BiometricAction { EnableWindowsV2 = "enableWindowsV2", IsWindowsV2Enabled = "isWindowsV2Enabled", + + EnableLinuxV2 = "enableLinuxV2", + IsLinuxV2Enabled = "isLinuxV2Enabled", } export type BiometricMessage = diff --git a/apps/desktop/tailwind.config.js b/apps/desktop/tailwind.config.js index bf65ae8d7cb..e67c0c38010 100644 --- a/apps/desktop/tailwind.config.js +++ b/apps/desktop/tailwind.config.js @@ -9,6 +9,7 @@ config.content = [ "../../libs/key-management-ui/src/**/*.{html,ts}", "../../libs/angular/src/**/*.{html,ts}", "../../libs/vault/src/**/*.{html,ts,mdx}", + "../../libs/pricing/src/**/*.{html,ts}", ]; module.exports = config; diff --git a/apps/web/package.json b/apps/web/package.json index 1052630acd0..ddcf1576743 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.10.1", + "version": "2025.11.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/organizations/guards/org-policy.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-policy.guard.ts new file mode 100644 index 00000000000..5964601fbe7 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/guards/org-policy.guard.ts @@ -0,0 +1,70 @@ +import { inject } from "@angular/core"; +import { CanActivateFn, Router } from "@angular/router"; +import { firstValueFrom, Observable, switchMap, tap } from "rxjs"; + +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { ToastService } from "@bitwarden/components"; +import { UserId } from "@bitwarden/user-core"; + +/** + * This guard is intended to prevent members of an organization from accessing + * routes based on compliance with organization + * policies. e.g Emergency access, which is a non-organization + * feature is restricted by the Auto Confirm policy. + */ +export function organizationPolicyGuard( + featureCallback: ( + userId: UserId, + configService: ConfigService, + policyService: PolicyService, + ) => Observable, +): CanActivateFn { + return async () => { + const router = inject(Router); + const toastService = inject(ToastService); + const i18nService = inject(I18nService); + const accountService = inject(AccountService); + const policyService = inject(PolicyService); + const configService = inject(ConfigService); + const syncService = inject(SyncService); + + const synced = await firstValueFrom( + accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => syncService.lastSync$(userId)), + ), + ); + + if (synced == null) { + await syncService.fullSync(false); + } + + const compliant = await firstValueFrom( + accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => featureCallback(userId, configService, policyService)), + tap((compliant) => { + if (typeof compliant !== "boolean") { + throw new Error("Feature callback must return a boolean."); + } + }), + ), + ); + + if (!compliant) { + toastService.showToast({ + variant: "error", + message: i18nService.t("noPageAccess"), + }); + + return router.createUrlTree(["/"]); + } + + return compliant; + }; +} 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 e5af0faa164..accb5f77fdc 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 @@ -2,17 +2,12 @@ - - - + > -

    +

    {{ "upgradeEventLogTitleMessage" | i18n }}

    diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.html b/apps/web/src/app/admin-console/organizations/manage/groups.component.html index 62d0b5b874b..aa4f2ccf138 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.html @@ -34,7 +34,7 @@ (change)="toggleAllVisible($event)" id="selectAll" /> -

    @let showBadge = firstTimeDialog(); @if (showBadge) { - {{ "availableNow" | i18n }} + {{ "availableNow" | i18n }} } - {{ (firstTimeDialog ? "autoConfirm" : "editPolicy") | i18n }} - @if (!firstTimeDialog) { + {{ (showBadge ? "autoConfirm" : "editPolicy") | i18n }} + @if (!showBadge) { {{ policy.name | i18n }} @@ -63,7 +63,9 @@ bitFormButton type="submit" > - @if (autoConfirmEnabled$ | async) { + @let autoConfirmEnabled = autoConfirmEnabled$ | async; + @let managePoliciesOnly = managePoliciesOnly$ | async; + @if (autoConfirmEnabled || managePoliciesOnly) { {{ "save" | i18n }} } @else { {{ "continue" | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts index 55894aafd53..99d484f04f2 100644 --- a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts @@ -22,6 +22,8 @@ import { tap, } from "rxjs"; +import { AutomaticUserConfirmationService } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -30,6 +32,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { getById } from "@bitwarden/common/platform/misc"; import { DIALOG_DATA, DialogConfig, @@ -83,6 +86,15 @@ export class AutoConfirmPolicyDialogComponent switchMap((userId) => this.policyService.policies$(userId)), map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm)?.enabled ?? false), ); + // Users with manage policies custom permission should not see the dialog's second step since + // they do not have permission to configure the setting. This will only allow them to configure + // the policy. + protected managePoliciesOnly$: Observable = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.organizationService.organizations$(userId)), + getById(this.data.organizationId), + map((organization) => (!organization?.isAdmin && organization?.canManagePolicies) ?? false), + ); private readonly submitPolicy: Signal | undefined> = viewChild("step0"); private readonly openExtension: Signal | undefined> = viewChild("step1"); @@ -105,8 +117,10 @@ export class AutoConfirmPolicyDialogComponent toastService: ToastService, configService: ConfigService, keyService: KeyService, + private organizationService: OrganizationService, private policyService: PolicyService, private router: Router, + private autoConfirmService: AutomaticUserConfirmationService, ) { super( data, @@ -146,22 +160,34 @@ export class AutoConfirmPolicyDialogComponent tap((singleOrgPolicyEnabled) => this.policyComponent?.setSingleOrgEnabled(singleOrgPolicyEnabled), ), - map((singleOrgPolicyEnabled) => [ - { - sideEffect: () => this.handleSubmit(singleOrgPolicyEnabled ?? false), - footerContent: this.submitPolicy, - titleContent: this.submitPolicyTitle, - }, - { - sideEffect: () => this.openBrowserExtension(), - footerContent: this.openExtension, - titleContent: this.openExtensionTitle, - }, - ]), + switchMap((singleOrgPolicyEnabled) => this.buildMultiStepSubmit(singleOrgPolicyEnabled)), shareReplay({ bufferSize: 1, refCount: true }), ); } + private buildMultiStepSubmit(singleOrgPolicyEnabled: boolean): Observable { + return this.managePoliciesOnly$.pipe( + map((managePoliciesOnly) => { + const submitSteps = [ + { + sideEffect: () => this.handleSubmit(singleOrgPolicyEnabled ?? false), + footerContent: this.submitPolicy, + titleContent: this.submitPolicyTitle, + }, + ]; + + if (!managePoliciesOnly) { + submitSteps.push({ + sideEffect: () => this.openBrowserExtension(), + footerContent: this.openExtension, + titleContent: this.openExtensionTitle, + }); + } + return submitSteps; + }), + ); + } + private async handleSubmit(singleOrgEnabled: boolean) { if (!singleOrgEnabled) { await this.submitSingleOrg(); @@ -185,6 +211,17 @@ export class AutoConfirmPolicyDialogComponent autoConfirmRequest, ); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const currentAutoConfirmState = await firstValueFrom( + this.autoConfirmService.configuration$(userId), + ); + + await this.autoConfirmService.upsert(userId, { + ...currentAutoConfirmState, + showSetupDialog: false, + }); + this.toastService.showToast({ variant: "success", message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)), @@ -197,7 +234,6 @@ export class AutoConfirmPolicyDialogComponent private async submitSingleOrg(): Promise { const singleOrgRequest: PolicyRequest = { - type: PolicyType.SingleOrg, enabled: true, data: null, }; diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts index 54d4491156c..c1b175fa988 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts @@ -109,7 +109,6 @@ export abstract class BasePolicyEditComponent implements OnInit { } const request: PolicyRequest = { - type: this.policy.type, enabled: this.enabled.value ?? false, data: this.buildRequestData(), }; diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 624e5132faf..3042be240f7 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -2,3 +2,6 @@ export { PoliciesComponent } from "./policies.component"; export { ossPolicyEditRegister } from "./policy-edit-register"; export { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component"; export { POLICY_EDIT_REGISTER } from "./policy-register-token"; +export { AutoConfirmPolicyDialogComponent } from "./auto-confirm-edit-policy-dialog.component"; +export { AutoConfirmPolicy } from "./policy-edit-definitions"; +export { PolicyEditDialogResult } from "./policy-edit-dialog.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html index 8334b451d22..54f166b662e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html @@ -7,7 +7,7 @@
    • - + {{ "autoConfirmAcceptSecurityRiskTitle" | i18n }} {{ "autoConfirmAcceptSecurityRiskDescription" | i18n }} @@ -19,19 +19,19 @@
    • @if (singleOrgEnabled$ | async) { - + {{ "autoConfirmSingleOrgExemption" | i18n }} } @else { - + {{ "autoConfirmSingleOrgRequired" | i18n }} } - {{ "autoConfirmSingleOrgRequiredDescription" | i18n }} + {{ "autoConfirmSingleOrgRequiredDesc" | i18n }}
    • - + {{ "autoConfirmNoEmergencyAccess" | i18n }} {{ "autoConfirmNoEmergencyAccessDescription" | i18n }} @@ -47,12 +47,12 @@
      -
    1. 1. {{ "autoConfirmStep1" | i18n }}
    2. +
    3. 1. {{ "autoConfirmExtension1" | i18n }}
    4. - 2. {{ "autoConfirmStep2a" | i18n }} + 2. {{ "autoConfirmExtension2" | i18n }} - {{ "autoConfirmStep2b" | i18n }} + {{ "autoConfirmExtension3" | i18n }}
    diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/index.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/index.ts index 7373e1ff888..9b46e228af9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/index.ts @@ -10,6 +10,7 @@ export { RestrictedItemTypesPolicy } from "./restricted-item-types.component"; export { SendOptionsPolicy } from "./send-options.component"; export { SingleOrgPolicy } from "./single-org.component"; export { TwoFactorAuthenticationPolicy } from "./two-factor-authentication.component"; +export { UriMatchDefaultPolicy } from "./uri-match-default.component"; export { vNextOrganizationDataOwnershipPolicy, vNextOrganizationDataOwnershipPolicyComponent, diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.html new file mode 100644 index 00000000000..399a4ad2dcd --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.html @@ -0,0 +1,22 @@ + + {{ "requireSsoPolicyReq" | i18n }} + + + + + {{ "turnOn" | i18n }} + + +
    + + {{ "uriMatchDetectionOptionsLabel" | i18n }} + + + + +
    diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts new file mode 100644 index 00000000000..5c0b667bea2 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/uri-match-default.component.ts @@ -0,0 +1,72 @@ +import { Component, ChangeDetectionStrategy } from "@angular/core"; +import { FormBuilder, FormControl, Validators } from "@angular/forms"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { PolicyRequest } from "@bitwarden/common/admin-console/models/request/policy.request"; +import { + UriMatchStrategy, + UriMatchStrategySetting, +} from "@bitwarden/common/models/domain/domain-service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { SharedModule } from "../../../../shared"; +import { BasePolicyEditDefinition, BasePolicyEditComponent } from "../base-policy-edit.component"; + +export class UriMatchDefaultPolicy extends BasePolicyEditDefinition { + name = "uriMatchDetectionPolicy"; + description = "uriMatchDetectionPolicyDesc"; + type = PolicyType.UriMatchDefaults; + component = UriMatchDefaultPolicyComponent; +} +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "uri-match-default.component.html", + imports: [SharedModule], +}) +export class UriMatchDefaultPolicyComponent extends BasePolicyEditComponent { + uriMatchOptions: { label: string; value: UriMatchStrategySetting | null; disabled?: boolean }[]; + + constructor( + private formBuilder: FormBuilder, + private i18nService: I18nService, + ) { + super(); + + this.data = this.formBuilder.group({ + uriMatchDetection: new FormControl(UriMatchStrategy.Domain, { + validators: [Validators.required], + nonNullable: true, + }), + }); + + this.uriMatchOptions = [ + { label: i18nService.t("baseDomain"), value: UriMatchStrategy.Domain }, + { label: i18nService.t("host"), value: UriMatchStrategy.Host }, + { label: i18nService.t("exact"), value: UriMatchStrategy.Exact }, + { label: i18nService.t("never"), value: UriMatchStrategy.Never }, + ]; + } + + protected loadData() { + const uriMatchDetection = this.policyResponse?.data?.uriMatchDetection; + + this.data?.patchValue({ + uriMatchDetection: uriMatchDetection, + }); + } + + protected buildRequestData() { + return { + uriMatchDetection: this.data?.value?.uriMatchDetection, + }; + } + + async buildRequest(): Promise { + const request = await super.buildRequest(); + if (request.data?.uriMatchDetection == null) { + throw new Error(this.i18nService.t("invalidUriMatchDefaultPolicySetting")); + } + + return request; + } +} diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts index 627f5762eda..a15c51ebf70 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts @@ -74,7 +74,6 @@ export class vNextOrganizationDataOwnershipPolicyComponent const request: VNextPolicyRequest = { policy: { - type: this.policy.type, enabled: this.enabled.value ?? false, data: this.buildRequestData(), }, diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-register.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-register.ts index ca44818764c..a4bdece0a7b 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-register.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-register.ts @@ -13,6 +13,7 @@ import { SendOptionsPolicy, SingleOrgPolicy, TwoFactorAuthenticationPolicy, + UriMatchDefaultPolicy, vNextOrganizationDataOwnershipPolicy, } from "./policy-edit-definitions"; @@ -34,5 +35,6 @@ export const ossPolicyEditRegister: BasePolicyEditDefinition[] = [ new SendOptionsPolicy(), new RestrictedItemTypesPolicy(), new DesktopAutotypeDefaultSettingPolicy(), + new UriMatchDefaultPolicy(), new AutoConfirmPolicy(), ]; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html index 116af15f579..75d089a8764 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.html @@ -100,7 +100,7 @@
    {{ permissionLabelId(item.readonlyPermission) | i18n }} diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index 3c400decd52..568c4922337 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -15,8 +15,9 @@ import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/ import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -43,7 +44,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { return; } - value.plan = PlanType.FamiliesAnnually; + value.plan = this._familyPlan; value.productTier = ProductTierType.Families; value.acceptingSponsorship = true; value.planSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise; @@ -63,13 +64,14 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { _selectedFamilyOrganizationId = ""; private _destroy = new Subject(); + private _familyPlan: PlanType; formGroup = this.formBuilder.group({ selectedFamilyOrganizationId: ["", Validators.required], }); constructor( private router: Router, - private platformUtilsService: PlatformUtilsService, + private configService: ConfigService, private i18nService: I18nService, private route: ActivatedRoute, private apiService: ApiService, @@ -120,6 +122,13 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { this.badToken = !this.preValidateSponsorshipResponse.isTokenValid; } + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + this._familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; + this.loading = false; }); diff --git a/apps/web/src/app/admin-console/settings/create-organization.component.ts b/apps/web/src/app/admin-console/settings/create-organization.component.ts index bdf450fb265..45ce89c0e3d 100644 --- a/apps/web/src/app/admin-console/settings/create-organization.component.ts +++ b/apps/web/src/app/admin-console/settings/create-organization.component.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; import { first } from "rxjs/operators"; import { PlanType, ProductTierType, ProductType } from "@bitwarden/common/billing/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { OrganizationPlansComponent } from "../../billing"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -17,15 +19,27 @@ import { SharedModule } from "../../shared"; templateUrl: "create-organization.component.html", imports: [SharedModule, OrganizationPlansComponent, HeaderModule], }) -export class CreateOrganizationComponent { +export class CreateOrganizationComponent implements OnInit { protected secretsManager = false; protected plan: PlanType = PlanType.Free; protected productTier: ProductTierType = ProductTierType.Free; - constructor(private route: ActivatedRoute) { + constructor( + private route: ActivatedRoute, + private configService: ConfigService, + ) {} + + async ngOnInit(): Promise { + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + const familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; + this.route.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((qParams) => { if (qParams.plan === "families" || qParams.productTier == ProductTierType.Families) { - this.plan = PlanType.FamiliesAnnually; + this.plan = familyPlan; this.productTier = ProductTierType.Families; } else if (qParams.plan === "teams" || qParams.productTier == ProductTierType.Teams) { this.plan = PlanType.TeamsAnnually; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 13c4207992c..30dbee9fac5 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -8,6 +8,7 @@ import { Subject, filter, firstValueFrom, map, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n"; +import { LockService } from "@bitwarden/auth/common"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -16,7 +17,6 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service" import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -58,8 +58,8 @@ export class AppComponent implements OnDestroy, OnInit { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private ngZone: NgZone, - private vaultTimeoutService: VaultTimeoutService, private keyService: KeyService, + private lockService: LockService, private collectionService: CollectionService, private searchService: SearchService, private serverNotificationsService: ServerNotificationsService, @@ -113,11 +113,13 @@ export class AppComponent implements OnDestroy, OnInit { // note: the message.logoutReason isn't consumed anymore because of the process reload clearing any toasts. await this.logOut(message.redirect); break; - case "lockVault": - await this.vaultTimeoutService.lock(); + case "lockVault": { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + await this.lockService.lock(userId); break; + } case "locked": - await this.processReloadService.startProcessReload(this.authService); + await this.processReloadService.startProcessReload(); break; case "lockedUrl": break; @@ -147,18 +149,6 @@ export class AppComponent implements OnDestroy, OnInit { } break; } - case "premiumRequired": { - const premiumConfirmed = await this.dialogService.openSimpleDialog({ - title: { key: "premiumRequired" }, - content: { key: "premiumRequiredDesc" }, - acceptButtonText: { key: "upgrade" }, - type: "success", - }); - if (premiumConfirmed) { - await this.router.navigate(["settings/subscription/premium"]); - } - break; - } case "emailVerificationRequired": { const emailVerificationConfirmed = await this.dialogService.openSimpleDialog({ title: { key: "emailVerificationRequired" }, @@ -279,7 +269,7 @@ export class AppComponent implements OnDestroy, OnInit { await this.router.navigate(["/"]); } - await this.processReloadService.startProcessReload(this.authService); + await this.processReloadService.startProcessReload(); // Normally we would need to reset the loading state to false or remove the layout_frontend // class from the body here, but the process reload completely reloads the app so 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 dc85668c8ec..9c033b88a75 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -113,14 +113,37 @@ export class RecoverTwoFactorComponent implements OnInit { await this.router.navigate(["/settings/security/two-factor"]); } catch (error: unknown) { if (error instanceof ErrorResponse) { - this.logService.error("Error logging in automatically: ", error.message); - - if (error.message.includes("Two-step token is invalid")) { - this.formGroup.get("recoveryCode")?.setErrors({ - invalidRecoveryCode: { message: this.i18nService.t("invalidRecoveryCode") }, + if ( + error.message.includes( + "Two-factor recovery has been performed. SSO authentication is required.", + ) + ) { + // [PM-21153]: Organization users with as SSO requirement need to be able to recover 2FA, + // but still be bound by the SSO requirement to log in. Therefore, we show a success toast for recovering 2FA, + // but then inform them that they need to log in via SSO and redirect them to the login page. + // The response tested here is a specific message for this scenario from request validation. + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepRecoverDisabled"), }); + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("ssoLoginIsRequired"), + }); + + await this.router.navigate(["/login"]); } else { - this.validationService.showError(error.message); + this.logService.error("Error logging in automatically: ", error.message); + + if (error.message.includes("Two-step token is invalid")) { + this.formGroup.get("recoveryCode")?.setErrors({ + invalidRecoveryCode: { message: this.i18nService.t("invalidRecoveryCode") }, + }); + } else { + this.validationService.showError(error.message); + } } } else { this.logService.error("Error logging in automatically: ", error); diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index c2b8127ec34..7ef94706ef6 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -96,15 +96,6 @@ export class EmergencyAccessComponent implements OnInit { this.loaded = true; } - async premiumRequired() { - const canAccessPremium = await firstValueFrom(this.canAccessPremium$); - - if (!canAccessPremium) { - this.messagingService.send("premiumRequired"); - return; - } - } - edit = async (details: GranteeEmergencyAccess) => { const canAccessPremium = await firstValueFrom(this.canAccessPremium$); const dialogRef = EmergencyAccessAddEditComponent.open(this.dialogService, { diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 60993924ded..d13987f2e8b 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -8,6 +8,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -16,6 +17,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId, EmergencyAccessId } from "@bitwarden/common/types/guid"; +import { CipherRiskService } from "@bitwarden/common/vault/abstractions/cipher-risk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -68,6 +70,12 @@ describe("EmergencyViewDialogComponent", () => { useValue: { environment$: of({ getIconsUrl: () => "https://icons.example.com" }) }, }, { provide: DomainSettingsService, useValue: { showFavicons$: of(true) } }, + { provide: CipherRiskService, useValue: mock() }, + { + provide: BillingAccountProfileStateService, + useValue: mock(), + }, + { provide: ConfigService, useValue: mock() }, ], }) .overrideComponent(EmergencyViewDialogComponent, { @@ -78,7 +86,6 @@ describe("EmergencyViewDialogComponent", () => { provide: ChangeLoginPasswordService, useValue: ChangeLoginPasswordService, }, - { provide: ConfigService, useValue: ConfigService }, { provide: CipherService, useValue: mock() }, ], }, @@ -89,7 +96,6 @@ describe("EmergencyViewDialogComponent", () => { provide: ChangeLoginPasswordService, useValue: mock(), }, - { provide: ConfigService, useValue: mock() }, { provide: CipherService, useValue: mock() }, ], }, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index c614e45e577..ad8d401d3fc 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -27,7 +27,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { enabled = false; authed = false; - protected hashedSecret: string | undefined; + protected secret: string | undefined; protected verificationType: VerificationType | undefined; protected componentName = ""; @@ -42,7 +42,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { ) {} protected auth(authResponse: AuthResponseBase) { - this.hashedSecret = authResponse.secret; + this.secret = authResponse.secret; this.verificationType = authResponse.verificationType; this.authed = true; } @@ -132,12 +132,12 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected async buildRequestModel( requestClass: new () => T, ) { - if (this.hashedSecret === undefined || this.verificationType === undefined) { + if (this.secret === undefined || this.verificationType === undefined) { throw new Error("User verification data is missing"); } return this.userVerificationService.buildRequest( { - secret: this.hashedSecret, + secret: this.secret, type: this.verificationType, }, requestClass, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index eec9f74dd60..c272a8e5b70 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -17,10 +17,10 @@
    • - + {{ "webAuthnkeyX" | i18n: (i + 1).toString() }} - + {{ k.name }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html index dbad422a32e..172646f5d4d 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html @@ -45,7 +45,7 @@
    -

    {{ "nfcSupport" | i18n }}

    +

    {{ "nfcSupport" | i18n }}

    {{ "twoFactorYubikeySupportsNfc" | i18n }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html index 16c3dcb3cda..ee2d4dd7b63 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html @@ -53,7 +53,7 @@

    {{ p.name }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index ef4d647a7d0..024455cc1bf 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -3,7 +3,6 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { first, - firstValueFrom, lastValueFrom, Observable, Subject, @@ -264,13 +263,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { } } - async premiumRequired() { - if (!(await firstValueFrom(this.canAccessPremium$))) { - this.messagingService.send("premiumRequired"); - return; - } - } - protected getTwoFactorProviders() { return this.twoFactorApiService.getTwoFactorProviders(); } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts index 9baa93d38c0..075d3bdf562 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts @@ -1,15 +1,14 @@ -import { Component, EventEmitter, Inject, Output } from "@angular/core"; +import { Component, Inject } from "@angular/core"; import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms"; import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; 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 { VerificationWithSecret } 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 { @@ -45,14 +44,10 @@ type TwoFactorVerifyDialogData = { export class TwoFactorVerifyComponent { type: TwoFactorProviderType; organizationId: string; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() onAuthed = new EventEmitter>(); - formPromise: Promise | undefined; protected formGroup = new FormGroup({ - secret: new FormControl(null), + secret: new FormControl(null), }); invalidSecret: boolean = false; @@ -69,24 +64,19 @@ export class TwoFactorVerifyComponent { submit = async () => { try { - let hashedSecret = ""; if (!this.formGroup.value.secret) { throw new Error("Secret is required"); } const secret = this.formGroup.value.secret!; this.formPromise = this.userVerificationService.buildRequest(secret).then((request) => { - hashedSecret = - secret.type === VerificationType.MasterPassword - ? request.masterPasswordHash - : request.otp; return this.apiCall(request); }); const response = await this.formPromise; this.dialogRef.close({ response: response, - secret: hashedSecret, + secret: secret.secret, verificationType: secret.type, }); } catch (e) { diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html index 7b1d859fb69..2ef177922a9 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html @@ -19,7 +19,6 @@ - {{ "beta" | i18n }} @@ -34,7 +33,7 @@ - +
    {{ credential.name }}{{ credential.name }} diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts index e8a278d8dd7..b2bc8e6c322 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, HostBinding, OnDestroy, OnInit } from "@angular/core"; import { Subject, switchMap, takeUntil } from "rxjs"; @@ -36,6 +34,8 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { protected credentials?: WebauthnLoginCredentialView[]; protected loading = true; + protected requireSsoPolicyEnabled = false; + constructor( private webauthnService: WebauthnLoginAdminService, private dialogService: DialogService, @@ -43,25 +43,6 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { private accountService: AccountService, ) {} - @HostBinding("attr.aria-busy") - get ariaBusy() { - return this.loading ? "true" : "false"; - } - - get hasCredentials() { - return this.credentials && this.credentials.length > 0; - } - - get hasData() { - return this.credentials !== undefined; - } - - get limitReached() { - return this.credentials?.length >= this.MaxCredentialCount; - } - - requireSsoPolicyEnabled = false; - ngOnInit(): void { this.accountService.activeAccount$ .pipe( @@ -90,6 +71,23 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { this.destroy$.complete(); } + @HostBinding("attr.aria-busy") + get ariaBusy() { + return this.loading ? "true" : "false"; + } + + get hasCredentials() { + return (this.credentials?.length ?? 0) > 0; + } + + get hasData() { + return this.credentials !== undefined; + } + + get limitReached() { + return (this.credentials?.length ?? 0) >= this.MaxCredentialCount; + } + protected createCredential() { openCreateCredentialDialog(this.dialogService, {}); } diff --git a/apps/web/src/app/billing/guards/has-premium.guard.ts b/apps/web/src/app/billing/guards/has-premium.guard.ts index 61853b25cb8..f10e75d8268 100644 --- a/apps/web/src/app/billing/guards/has-premium.guard.ts +++ b/apps/web/src/app/billing/guards/has-premium.guard.ts @@ -1,21 +1,21 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, - RouterStateSnapshot, - Router, CanActivateFn, + Router, + RouterStateSnapshot, UrlTree, } from "@angular/router"; -import { Observable, of } from "rxjs"; +import { from, Observable, of } from "rxjs"; import { switchMap, tap } from "rxjs/operators"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; /** - * CanActivate guard that checks if the user has premium and otherwise triggers the "premiumRequired" - * message and blocks navigation. + * CanActivate guard that checks if the user has premium and otherwise triggers the premium upgrade + * flow and blocks navigation. */ export function hasPremiumGuard(): CanActivateFn { return ( @@ -23,7 +23,7 @@ export function hasPremiumGuard(): CanActivateFn { _state: RouterStateSnapshot, ): Observable => { const router = inject(Router); - const messagingService = inject(MessagingService); + const premiumUpgradePromptService = inject(PremiumUpgradePromptService); const billingAccountProfileStateService = inject(BillingAccountProfileStateService); const accountService = inject(AccountService); @@ -33,10 +33,14 @@ export function hasPremiumGuard(): CanActivateFn { ? billingAccountProfileStateService.hasPremiumFromAnySource$(account.id) : of(false), ), - tap((userHasPremium: boolean) => { + switchMap((userHasPremium: boolean) => { + // Can't call async method inside observables so instead, wait for service then switch back to the boolean if (!userHasPremium) { - messagingService.send("premiumRequired"); + return from(premiumUpgradePromptService.promptForPremium()).pipe( + switchMap(() => of(userHasPremium)), + ); } + return of(userHasPremium); }), // Prevent trapping the user on the login page, since that's an awful UX flow tap((userHasPremium: boolean) => { diff --git a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts index 26d0c43ff8f..cdccaaab8ab 100644 --- a/apps/web/src/app/billing/individual/individual-billing-routing.module.ts +++ b/apps/web/src/app/billing/individual/individual-billing-routing.module.ts @@ -2,15 +2,15 @@ import { inject, NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { map } from "rxjs"; -import { componentRouteSwap } from "@bitwarden/angular/utils/component-route-swap"; 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 { AccountPaymentDetailsComponent } from "@bitwarden/web-vault/app/billing/individual/payment-details/account-payment-details.component"; +import { SelfHostedPremiumComponent } from "@bitwarden/web-vault/app/billing/individual/premium/self-hosted-premium.component"; import { BillingHistoryViewComponent } from "./billing-history-view.component"; -import { PremiumVNextComponent } from "./premium/premium-vnext.component"; -import { PremiumComponent } from "./premium/premium.component"; +import { CloudHostedPremiumVNextComponent } from "./premium/cloud-hosted-premium-vnext.component"; +import { CloudHostedPremiumComponent } from "./premium/cloud-hosted-premium.component"; import { SubscriptionComponent } from "./subscription.component"; import { UserSubscriptionComponent } from "./user-subscription.component"; @@ -26,22 +26,55 @@ const routes: Routes = [ component: UserSubscriptionComponent, data: { titleId: "premiumMembership" }, }, - ...componentRouteSwap( - PremiumComponent, - PremiumVNextComponent, - () => { - const configService = inject(ConfigService); - const platformUtilsService = inject(PlatformUtilsService); + /** + * Three-Route Matching Strategy for /premium: + * + * Routes are evaluated in order using canMatch guards. The first route that matches will be selected. + * + * 1. Self-Hosted Environment → SelfHostedPremiumComponent + * - Matches when platformUtilsService.isSelfHost() === true + * + * 2. Cloud-Hosted + Feature Flag Enabled → CloudHostedPremiumVNextComponent + * - Only evaluated if Route 1 doesn't match (not self-hosted) + * - Matches when PM24033PremiumUpgradeNewDesign feature flag === true + * + * 3. Cloud-Hosted + Feature Flag Disabled → CloudHostedPremiumComponent (Fallback) + * - No canMatch guard, so this always matches as the fallback route + * - Used when neither Route 1 nor Route 2 match + */ + // Route 1: Self-Hosted -> SelfHostedPremiumComponent + { + path: "premium", + component: SelfHostedPremiumComponent, + data: { titleId: "goPremium" }, + canMatch: [ + () => { + const platformUtilsService = inject(PlatformUtilsService); + return platformUtilsService.isSelfHost(); + }, + ], + }, + // Route 2: Cloud Hosted + FF -> CloudHostedPremiumVNextComponent + { + path: "premium", + component: CloudHostedPremiumVNextComponent, + data: { titleId: "goPremium" }, + canMatch: [ + () => { + const configService = inject(ConfigService); - return configService - .getFeatureFlag$(FeatureFlag.PM24033PremiumUpgradeNewDesign) - .pipe(map((flagValue) => flagValue === true && !platformUtilsService.isSelfHost())); - }, - { - data: { titleId: "goPremium" }, - path: "premium", - }, - ), + return configService + .getFeatureFlag$(FeatureFlag.PM24033PremiumUpgradeNewDesign) + .pipe(map((flagValue) => flagValue === true)); + }, + ], + }, + // Route 3: Cloud Hosted + FF Disabled -> CloudHostedPremiumComponent (Fallback) + { + path: "premium", + component: CloudHostedPremiumComponent, + data: { titleId: "goPremium" }, + }, { path: "payment-details", component: AccountPaymentDetailsComponent, diff --git a/apps/web/src/app/billing/individual/individual-billing.module.ts b/apps/web/src/app/billing/individual/individual-billing.module.ts index 56c40002f1d..200df5d9f07 100644 --- a/apps/web/src/app/billing/individual/individual-billing.module.ts +++ b/apps/web/src/app/billing/individual/individual-billing.module.ts @@ -11,7 +11,7 @@ import { BillingSharedModule } from "../shared"; import { BillingHistoryViewComponent } from "./billing-history-view.component"; import { IndividualBillingRoutingModule } from "./individual-billing-routing.module"; -import { PremiumComponent } from "./premium/premium.component"; +import { CloudHostedPremiumComponent } from "./premium/cloud-hosted-premium.component"; import { SubscriptionComponent } from "./subscription.component"; import { UserSubscriptionComponent } from "./user-subscription.component"; @@ -28,7 +28,7 @@ import { UserSubscriptionComponent } from "./user-subscription.component"; SubscriptionComponent, BillingHistoryViewComponent, UserSubscriptionComponent, - PremiumComponent, + CloudHostedPremiumComponent, ], }) export class IndividualBillingModule {} diff --git a/apps/web/src/app/billing/individual/premium/premium-vnext.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html similarity index 97% rename from apps/web/src/app/billing/individual/premium/premium-vnext.component.html rename to apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html index ee2bef9baa3..6b168901b2e 100644 --- a/apps/web/src/app/billing/individual/premium/premium-vnext.component.html +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.html @@ -7,7 +7,7 @@ -

    +

    {{ "upgradeCompleteSecurity" | i18n }}

    diff --git a/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts similarity index 92% rename from apps/web/src/app/billing/individual/premium/premium-vnext.component.ts rename to apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts index d25e035d1be..9fb34a6ccf0 100644 --- a/apps/web/src/app/billing/individual/premium/premium-vnext.component.ts +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium-vnext.component.ts @@ -16,7 +16,11 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; +import { + PersonalSubscriptionPricingTier, + PersonalSubscriptionPricingTierIds, +} from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { SyncService } from "@bitwarden/common/platform/sync"; import { BadgeModule, @@ -28,12 +32,7 @@ import { import { PricingCardComponent } from "@bitwarden/pricing"; import { I18nPipe } from "@bitwarden/ui-common"; -import { SubscriptionPricingService } from "../../services/subscription-pricing.service"; import { BitwardenSubscriber, mapAccountToSubscriber } from "../../types"; -import { - PersonalSubscriptionPricingTier, - PersonalSubscriptionPricingTierIds, -} from "../../types/subscription-pricing-tier"; import { UnifiedUpgradeDialogComponent, UnifiedUpgradeDialogParams, @@ -52,7 +51,7 @@ const RouteParamValues = { // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ - templateUrl: "./premium-vnext.component.html", + templateUrl: "./cloud-hosted-premium-vnext.component.html", standalone: true, imports: [ CommonModule, @@ -64,7 +63,7 @@ const RouteParamValues = { PricingCardComponent, ], }) -export class PremiumVNextComponent { +export class CloudHostedPremiumVNextComponent { protected hasPremiumFromAnyOrganization$: Observable; protected hasPremiumPersonally$: Observable; protected shouldShowNewDesign$: Observable; @@ -81,22 +80,18 @@ export class PremiumVNextComponent { features: string[]; }>; protected subscriber!: BitwardenSubscriber; - protected isSelfHost = false; private destroyRef = inject(DestroyRef); constructor( private accountService: AccountService, private apiService: ApiService, private dialogService: DialogService, - private platformUtilsService: PlatformUtilsService, private syncService: SyncService, private billingAccountProfileStateService: BillingAccountProfileStateService, - private subscriptionPricingService: SubscriptionPricingService, + private subscriptionPricingService: SubscriptionPricingServiceAbstraction, private router: Router, private activatedRoute: ActivatedRoute, ) { - this.isSelfHost = this.platformUtilsService.isSelfHost(); - this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( switchMap((account) => account @@ -187,10 +182,12 @@ export class PremiumVNextComponent { this.shouldShowUpgradeDialogOnInit$ .pipe( - switchMap(async (shouldShowUpgradeDialogOnInit) => { + switchMap((shouldShowUpgradeDialogOnInit) => { if (shouldShowUpgradeDialogOnInit) { - from(this.openUpgradeDialog("Premium")); + return from(this.openUpgradeDialog("Premium")); } + // Return an Observable that completes immediately when dialog should not be shown + return of(void 0); }), takeUntilDestroyed(this.destroyRef), ) diff --git a/apps/web/src/app/billing/individual/premium/premium.component.html b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html similarity index 88% rename from apps/web/src/app/billing/individual/premium/premium.component.html rename to apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html index 39b32be0853..63c26bd61f1 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.html +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.html @@ -10,7 +10,7 @@ } @else { -

    {{ "goPremium" | i18n }}

    +

    {{ "goPremium" | i18n }}

    -

    +

    {{ "premiumPriceWithFamilyPlan" | i18n: (premiumPrice$ | async | currency: "$") : familyPlanMaxUserCount @@ -65,24 +65,9 @@ {{ "bitwardenFamiliesPlan" | i18n }}

    - - {{ "purchasePremium" | i18n }} -
    - - - - +

    {{ "addons" | i18n }}

    diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts similarity index 89% rename from apps/web/src/app/billing/individual/premium/premium.component.ts rename to apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts index 6754f4c9f50..fceeeedf170 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/cloud-hosted-premium.component.ts @@ -5,6 +5,7 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { + catchError, combineLatest, concatMap, filter, @@ -12,10 +13,9 @@ import { map, Observable, of, + shareReplay, startWith, switchMap, - catchError, - shareReplay, } from "rxjs"; import { debounceTime } from "rxjs/operators"; @@ -23,9 +23,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; +import { DefaultSubscriptionPricingService } from "@bitwarden/common/billing/services/subscription-pricing.service"; +import { PersonalSubscriptionPricingTierIds } from "@bitwarden/common/billing/types/subscription-pricing-tier"; 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 { SyncService } from "@bitwarden/common/platform/sync"; import { ToastService } from "@bitwarden/components"; import { SubscriberBillingClient, TaxClient } from "@bitwarden/web-vault/app/billing/clients"; @@ -35,21 +36,19 @@ import { getBillingAddressFromForm, } from "@bitwarden/web-vault/app/billing/payment/components"; import { - tokenizablePaymentMethodToLegacyEnum, NonTokenizablePaymentMethods, + tokenizablePaymentMethodToLegacyEnum, } from "@bitwarden/web-vault/app/billing/payment/types"; -import { SubscriptionPricingService } from "@bitwarden/web-vault/app/billing/services/subscription-pricing.service"; import { mapAccountToSubscriber } from "@bitwarden/web-vault/app/billing/types"; -import { PersonalSubscriptionPricingTierIds } from "@bitwarden/web-vault/app/billing/types/subscription-pricing-tier"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ - templateUrl: "./premium.component.html", + templateUrl: "./cloud-hosted-premium.component.html", standalone: false, providers: [SubscriberBillingClient, TaxClient], }) -export class PremiumComponent { +export class CloudHostedPremiumComponent { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent!: EnterPaymentMethodComponent; @@ -121,7 +120,6 @@ export class PremiumComponent { ); protected cloudWebVaultURL: string; - protected isSelfHost = false; protected readonly familyPlanMaxUserCount = 6; constructor( @@ -130,17 +128,14 @@ export class PremiumComponent { private billingAccountProfileStateService: BillingAccountProfileStateService, private environmentService: EnvironmentService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private router: Router, private syncService: SyncService, private toastService: ToastService, private accountService: AccountService, private subscriberBillingClient: SubscriberBillingClient, private taxClient: TaxClient, - private subscriptionPricingService: SubscriptionPricingService, + private subscriptionPricingService: DefaultSubscriptionPricingService, ) { - this.isSelfHost = this.platformUtilsService.isSelfHost(); - this.hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id), @@ -231,7 +226,10 @@ export class PremiumComponent { const formData = new FormData(); formData.append("paymentMethodType", paymentMethodType.toString()); formData.append("paymentToken", paymentToken); - formData.append("additionalStorageGb", this.formGroup.value.additionalStorage.toString()); + formData.append( + "additionalStorageGb", + (this.formGroup.value.additionalStorage ?? 0).toString(), + ); formData.append("country", this.formGroup.value.billingAddress.country); formData.append("postalCode", this.formGroup.value.billingAddress.postalCode); @@ -239,12 +237,4 @@ export class PremiumComponent { await this.finalizeUpgrade(); await this.postFinalizeUpgrade(); }; - - protected get premiumURL(): string { - return `${this.cloudWebVaultURL}/#/settings/subscription/premium`; - } - - protected async onLicenseFileSelectedChanged(): Promise { - await this.postFinalizeUpgrade(); - } } diff --git a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html new file mode 100644 index 00000000000..1e32e73c8f5 --- /dev/null +++ b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.html @@ -0,0 +1,49 @@ + + + +

    {{ "premiumUpgradeUnlockFeatures" | i18n }}

    +
      +
    • + + {{ "premiumSignUpStorage" | i18n }} +
    • +
    • + + {{ "premiumSignUpTwoStepOptions" | i18n }} +
    • +
    • + + {{ "premiumSignUpEmergency" | i18n }} +
    • +
    • + + {{ "premiumSignUpReports" | i18n }} +
    • +
    • + + {{ "premiumSignUpTotp" | i18n }} +
    • +
    • + + {{ "premiumSignUpSupport" | i18n }} +
    • +
    • + + {{ "premiumSignUpFuture" | i18n }} +
    • +
    + + {{ "purchasePremium" | i18n }} + +
    +
    + + + +
    diff --git a/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts new file mode 100644 index 00000000000..c28f2d45b6f --- /dev/null +++ b/apps/web/src/app/billing/individual/premium/self-hosted-premium.component.ts @@ -0,0 +1,79 @@ +import { Component } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { ActivatedRoute, Router } from "@angular/router"; +import { combineLatest, map, of, switchMap } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { ToastService } from "@bitwarden/components"; +import { BillingSharedModule } from "@bitwarden/web-vault/app/billing/shared"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + templateUrl: "./self-hosted-premium.component.html", + imports: [SharedModule, BillingSharedModule], +}) +export class SelfHostedPremiumComponent { + cloudPremiumPageUrl$ = this.environmentService.cloudWebVaultUrl$.pipe( + map((url) => `${url}/#/settings/subscription/premium`), + ); + + hasPremiumFromAnyOrganization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumFromAnyOrganization$(account.id) + : of(false), + ), + ); + + hasPremiumPersonally$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + account + ? this.billingAccountProfileStateService.hasPremiumPersonally$(account.id) + : of(false), + ), + ); + + onLicenseFileUploaded = async () => { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("premiumUpdated"), + }); + await this.navigateToSubscription(); + }; + + constructor( + private accountService: AccountService, + private activatedRoute: ActivatedRoute, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private environmentService: EnvironmentService, + private i18nService: I18nService, + private router: Router, + private toastService: ToastService, + ) { + combineLatest([this.hasPremiumFromAnyOrganization$, this.hasPremiumPersonally$]) + .pipe( + takeUntilDestroyed(), + switchMap(([hasPremiumFromAnyOrganization, hasPremiumPersonally]) => { + if (hasPremiumFromAnyOrganization) { + return this.navigateToVault(); + } + if (hasPremiumPersonally) { + return this.navigateToSubscription(); + } + + return of(true); + }), + ) + .subscribe(); + } + + navigateToSubscription = () => + this.router.navigate(["../user-subscription"], { relativeTo: this.activatedRoute }); + navigateToVault = () => this.router.navigate(["/vault"]); +} diff --git a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts index ea74eb67ffc..b18e3a7f5c3 100644 --- a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.spec.ts @@ -7,16 +7,21 @@ import { AccountService, Account } from "@bitwarden/common/auth/abstractions/acc import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync/sync.service"; import { DialogRef, DialogService } from "@bitwarden/components"; +import { StateProvider } from "@bitwarden/state"; import { UnifiedUpgradeDialogComponent, UnifiedUpgradeDialogStatus, } from "../unified-upgrade-dialog/unified-upgrade-dialog.component"; -import { UnifiedUpgradePromptService } from "./unified-upgrade-prompt.service"; +import { + UnifiedUpgradePromptService, + PREMIUM_MODAL_DISMISSED_KEY, +} from "./unified-upgrade-prompt.service"; describe("UnifiedUpgradePromptService", () => { let sut: UnifiedUpgradePromptService; @@ -29,6 +34,8 @@ describe("UnifiedUpgradePromptService", () => { const mockOrganizationService = mock(); const mockDialogOpen = jest.spyOn(UnifiedUpgradeDialogComponent, "open"); const mockPlatformUtilsService = mock(); + const mockStateProvider = mock(); + const mockLogService = mock(); /** * Creates a mock DialogRef that implements the required properties for testing @@ -59,6 +66,8 @@ describe("UnifiedUpgradePromptService", () => { mockDialogService, mockOrganizationService, mockPlatformUtilsService, + mockStateProvider, + mockLogService, ); } @@ -72,6 +81,7 @@ describe("UnifiedUpgradePromptService", () => { mockAccountService.activeAccount$ = accountSubject.asObservable(); mockPlatformUtilsService.isSelfHost.mockReturnValue(false); mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + mockStateProvider.getUserState$.mockReturnValue(of(false)); setupTestService(); }); @@ -82,6 +92,7 @@ describe("UnifiedUpgradePromptService", () => { describe("displayUpgradePromptConditionally", () => { beforeEach(() => { + accountSubject.next(mockAccount); // Reset account to mockAccount mockAccountService.activeAccount$ = accountSubject.asObservable(); mockDialogOpen.mockReset(); mockReset(mockDialogService); @@ -90,11 +101,16 @@ describe("UnifiedUpgradePromptService", () => { mockReset(mockVaultProfileService); mockReset(mockSyncService); mockReset(mockOrganizationService); + mockReset(mockStateProvider); // Mock sync service methods mockSyncService.fullSync.mockResolvedValue(true); mockSyncService.lastSync$.mockReturnValue(of(new Date())); mockReset(mockPlatformUtilsService); + + // Default: modal has not been dismissed + mockStateProvider.getUserState$.mockReturnValue(of(false)); + mockStateProvider.setUserState.mockResolvedValue(undefined); }); it("should subscribe to account and feature flag observables when checking display conditions", async () => { // Arrange @@ -256,5 +272,71 @@ describe("UnifiedUpgradePromptService", () => { expect(result).toBeNull(); expect(mockDialogOpen).not.toHaveBeenCalled(); }); + + it("should not show dialog when user has previously dismissed the modal", async () => { + // Arrange + mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); + mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); + const recentDate = new Date(); + recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old + mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate); + mockPlatformUtilsService.isSelfHost.mockReturnValue(false); + mockStateProvider.getUserState$.mockReturnValue(of(true)); // User has dismissed + setupTestService(); + + // Act + const result = await sut.displayUpgradePromptConditionally(); + + // Assert + expect(result).toBeNull(); + expect(mockDialogOpen).not.toHaveBeenCalled(); + }); + + it("should save dismissal state when user closes the dialog", async () => { + // Arrange + mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); + mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); + const recentDate = new Date(); + recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old + mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate); + mockPlatformUtilsService.isSelfHost.mockReturnValue(false); + + const expectedResult = { status: UnifiedUpgradeDialogStatus.Closed }; + mockDialogOpenMethod(createMockDialogRef(expectedResult)); + setupTestService(); + + // Act + await sut.displayUpgradePromptConditionally(); + + // Assert + expect(mockStateProvider.setUserState).toHaveBeenCalledWith( + PREMIUM_MODAL_DISMISSED_KEY, + true, + mockAccount.id, + ); + }); + + it("should not save dismissal state when user upgrades to premium", async () => { + // Arrange + mockConfigService.getFeatureFlag$.mockReturnValue(of(true)); + mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false)); + mockOrganizationService.memberOrganizations$.mockReturnValue(of([])); + const recentDate = new Date(); + recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old + mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate); + mockPlatformUtilsService.isSelfHost.mockReturnValue(false); + + const expectedResult = { status: UnifiedUpgradeDialogStatus.UpgradedToPremium }; + mockDialogOpenMethod(createMockDialogRef(expectedResult)); + setupTestService(); + + // Act + await sut.displayUpgradePromptConditionally(); + + // Assert + expect(mockStateProvider.setUserState).not.toHaveBeenCalled(); + }); }); }); diff --git a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts index cf5deaf37fa..3ea8f19341d 100644 --- a/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/services/unified-upgrade-prompt.service.ts @@ -8,16 +8,29 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SyncService } from "@bitwarden/common/platform/sync/sync.service"; import { UserId } from "@bitwarden/common/types/guid"; import { DialogRef, DialogService } from "@bitwarden/components"; +import { BILLING_DISK, StateProvider, UserKeyDefinition } from "@bitwarden/state"; import { UnifiedUpgradeDialogComponent, UnifiedUpgradeDialogResult, + UnifiedUpgradeDialogStatus, } from "../unified-upgrade-dialog/unified-upgrade-dialog.component"; +// State key for tracking premium modal dismissal +export const PREMIUM_MODAL_DISMISSED_KEY = new UserKeyDefinition( + BILLING_DISK, + "premiumModalDismissed", + { + deserializer: (value: boolean) => value, + clearOn: [], + }, +); + @Injectable({ providedIn: "root", }) @@ -32,6 +45,8 @@ export class UnifiedUpgradePromptService { private dialogService: DialogService, private organizationService: OrganizationService, private platformUtilsService: PlatformUtilsService, + private stateProvider: StateProvider, + private logService: LogService, ) {} private shouldShowPrompt$: Observable = this.accountService.activeAccount$.pipe( @@ -45,22 +60,36 @@ export class UnifiedUpgradePromptService { return of(false); } - const isProfileLessThanFiveMinutesOld = from( + const isProfileLessThanFiveMinutesOld$ = from( this.isProfileLessThanFiveMinutesOld(account.id), ); - const hasOrganizations = from(this.hasOrganizations(account.id)); + const hasOrganizations$ = from(this.hasOrganizations(account.id)); + const hasDismissedModal$ = this.hasDismissedModal$(account.id); return combineLatest([ - isProfileLessThanFiveMinutesOld, - hasOrganizations, + isProfileLessThanFiveMinutesOld$, + hasOrganizations$, this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog), + hasDismissedModal$, ]).pipe( - map(([isProfileLessThanFiveMinutesOld, hasOrganizations, hasPremium, isFlagEnabled]) => { - return ( - isProfileLessThanFiveMinutesOld && !hasOrganizations && !hasPremium && isFlagEnabled - ); - }), + map( + ([ + isProfileLessThanFiveMinutesOld, + hasOrganizations, + hasPremium, + isFlagEnabled, + hasDismissed, + ]) => { + return ( + isProfileLessThanFiveMinutesOld && + !hasOrganizations && + !hasPremium && + isFlagEnabled && + !hasDismissed + ); + }, + ), ); }), take(1), @@ -114,6 +143,17 @@ export class UnifiedUpgradePromptService { const result = await firstValueFrom(this.unifiedUpgradeDialogRef.closed); this.unifiedUpgradeDialogRef = null; + // Save dismissal state when the modal is closed without upgrading + if (result?.status === UnifiedUpgradeDialogStatus.Closed) { + try { + await this.stateProvider.setUserState(PREMIUM_MODAL_DISMISSED_KEY, true, account.id); + } catch (error) { + // Log the error but don't block the dialog from closing + // The modal will still close properly even if persistence fails + this.logService.error("Failed to save premium modal dismissal state:", error); + } + } + // Return the result or null if the dialog was dismissed without a result return result || null; } @@ -145,4 +185,15 @@ export class UnifiedUpgradePromptService { return memberOrganizations.length > 0; } + + /** + * Checks if the user has previously dismissed the premium modal + * @param userId User ID to check + * @returns Observable that emits true if modal was dismissed, false otherwise + */ + private hasDismissedModal$(userId: UserId): Observable { + return this.stateProvider + .getUserState$(PREMIUM_MODAL_DISMISSED_KEY, userId) + .pipe(map((dismissed) => dismissed ?? false)); + } } diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts index d0960251724..32c67df1434 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.spec.ts @@ -4,13 +4,13 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock } from "jest-mock-extended"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { DIALOG_DATA, DialogRef } from "@bitwarden/components"; - import { PersonalSubscriptionPricingTierId, PersonalSubscriptionPricingTierIds, -} from "../../../types/subscription-pricing-tier"; +} from "@bitwarden/common/billing/types/subscription-pricing-tier"; +import { UserId } from "@bitwarden/common/types/guid"; +import { DIALOG_DATA, DialogRef } from "@bitwarden/components"; + import { UpgradeAccountComponent, UpgradeAccountStatus, diff --git a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts index 077490cef43..07b21a9fb4b 100644 --- a/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component.ts @@ -4,6 +4,7 @@ import { Component, Inject, OnInit, signal } from "@angular/core"; import { Router } from "@angular/router"; import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { PersonalSubscriptionPricingTierId } from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { ButtonModule, @@ -15,7 +16,6 @@ import { import { AccountBillingClient, TaxClient } from "../../../clients"; import { BillingServicesModule } from "../../../services"; -import { PersonalSubscriptionPricingTierId } from "../../../types/subscription-pricing-tier"; import { UpgradeAccountComponent } from "../upgrade-account/upgrade-account.component"; import { UpgradePaymentService } from "../upgrade-payment/services/upgrade-payment.service"; import { diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.html b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.html index 6106c6b08bb..f1aebac7695 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.html +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.html @@ -16,7 +16,7 @@
    -

    +

    {{ dialogTitle() | i18n }}

    diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.spec.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.spec.ts index a6038873e83..add0eb0a011 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.spec.ts @@ -4,15 +4,15 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; +import { + PersonalSubscriptionPricingTier, + PersonalSubscriptionPricingTierIds, +} from "@bitwarden/common/billing/types/subscription-pricing-tier"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PricingCardComponent } from "@bitwarden/pricing"; import { BillingServicesModule } from "../../../services"; -import { SubscriptionPricingService } from "../../../services/subscription-pricing.service"; -import { - PersonalSubscriptionPricingTier, - PersonalSubscriptionPricingTierIds, -} from "../../../types/subscription-pricing-tier"; import { UpgradeAccountComponent, UpgradeAccountStatus } from "./upgrade-account.component"; @@ -20,7 +20,7 @@ describe("UpgradeAccountComponent", () => { let sut: UpgradeAccountComponent; let fixture: ComponentFixture; const mockI18nService = mock(); - const mockSubscriptionPricingService = mock(); + const mockSubscriptionPricingService = mock(); // Mock pricing tiers data const mockPricingTiers: PersonalSubscriptionPricingTier[] = [ @@ -57,7 +57,10 @@ describe("UpgradeAccountComponent", () => { imports: [NoopAnimationsModule, UpgradeAccountComponent, PricingCardComponent, CdkTrapFocus], providers: [ { provide: I18nService, useValue: mockI18nService }, - { provide: SubscriptionPricingService, useValue: mockSubscriptionPricingService }, + { + provide: SubscriptionPricingServiceAbstraction, + useValue: mockSubscriptionPricingService, + }, ], }) .overrideComponent(UpgradeAccountComponent, { @@ -170,7 +173,10 @@ describe("UpgradeAccountComponent", () => { ], providers: [ { provide: I18nService, useValue: mockI18nService }, - { provide: SubscriptionPricingService, useValue: mockSubscriptionPricingService }, + { + provide: SubscriptionPricingServiceAbstraction, + useValue: mockSubscriptionPricingService, + }, ], }) .overrideComponent(UpgradeAccountComponent, { diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts index 780b6bed433..a4089d7a47a 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-account/upgrade-account.component.ts @@ -2,22 +2,23 @@ import { CdkTrapFocus } from "@angular/cdk/a11y"; import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit, computed, input, output, signal } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { catchError, of } from "rxjs"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; -import { ButtonType, DialogModule } from "@bitwarden/components"; -import { PricingCardComponent } from "@bitwarden/pricing"; - -import { SharedModule } from "../../../../shared"; -import { BillingServicesModule } from "../../../services"; -import { SubscriptionPricingService } from "../../../services/subscription-pricing.service"; +import { SubscriptionPricingServiceAbstraction } from "@bitwarden/common/billing/abstractions/subscription-pricing.service.abstraction"; import { PersonalSubscriptionPricingTier, PersonalSubscriptionPricingTierId, PersonalSubscriptionPricingTierIds, SubscriptionCadence, SubscriptionCadenceIds, -} from "../../../types/subscription-pricing-tier"; +} from "@bitwarden/common/billing/types/subscription-pricing-tier"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; +import { ButtonType, DialogModule, ToastService } from "@bitwarden/components"; +import { PricingCardComponent } from "@bitwarden/pricing"; + +import { SharedModule } from "../../../../shared"; +import { BillingServicesModule } from "../../../services"; export const UpgradeAccountStatus = { Closed: "closed", @@ -72,14 +73,26 @@ export class UpgradeAccountComponent implements OnInit { constructor( private i18nService: I18nService, - private subscriptionPricingService: SubscriptionPricingService, + private subscriptionPricingService: SubscriptionPricingServiceAbstraction, + private toastService: ToastService, private destroyRef: DestroyRef, ) {} ngOnInit(): void { this.subscriptionPricingService .getPersonalSubscriptionPricingTiers$() - .pipe(takeUntilDestroyed(this.destroyRef)) + .pipe( + catchError((error: unknown) => { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("unexpectedError"), + }); + this.loading.set(false); + return of([]); + }), + takeUntilDestroyed(this.destroyRef), + ) .subscribe((plans) => { this.setupCardDetails(plans); this.loading.set(false); diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html index 2ffcd14fab0..a028839f8f0 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-nav-button/upgrade-nav-button/upgrade-nav-button.component.html @@ -4,7 +4,7 @@ is not supported by the button in the CL. -->

    - {{ "lastSync" | i18n }}: + {{ "lastSync" | i18n }}: {{ lastSyncDate | date: "medium" }}
    diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html index abd7bdb155a..a7b9196cc5e 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.html +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.html @@ -1,12 +1,12 @@ - + {{ dialogHeaderName }}

    {{ "upgradePlans" | i18n }}

    - {{ + {{ "selectAPlan" | i18n }} @@ -57,7 +57,7 @@ selectableProduct.productTier === productTypes.Enterprise && !isSubscriptionCanceled " - class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-bold tw-py-1" + class="tw-bg-secondary-100 tw-text-center !tw-border-0 tw-text-sm tw-font-medium tw-py-1" [ngClass]="{ 'tw-bg-primary-700 !tw-text-contrast': selectableProduct === selectedPlan, 'tw-bg-secondary-100': !(selectableProduct === selectedPlan), @@ -73,7 +73,7 @@ }" >

    {{ selectableProduct.nameLocalizationKey | i18n @@ -91,7 +91,7 @@ - + {{ (selectableProduct.isAnnual ? selectableProduct.PasswordManager.basePrice / 12 @@ -106,7 +106,7 @@ : ("monthPerMember" | i18n) }} - + @@ -128,7 +128,7 @@ selectableProduct.PasswordManager.hasAdditionalSeatsOption " > - {{ "costPerMember" | i18n @@ -155,7 +155,7 @@ " >

    {{ "bitwardenPasswordManager" | i18n }} @@ -182,7 +182,7 @@

    {{ "bitwardenSecretsManager" | i18n }} @@ -222,7 +222,7 @@

    {{ "bitwardenPasswordManager" | i18n }} @@ -274,7 +274,7 @@

    - {{ "total" | i18n }}: {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} USD @@ -402,7 +402,7 @@

    -

    +

    {{ "passwordManager" | i18n }}

    -

    +

    {{ "secretsManager" | i18n }}

    -

    +

    {{ "passwordManager" | i18n }}

    -

    +

    {{ "secretsManager" | i18n }}

    -

    +

    {{ "secretsManager" | i18n }}

    {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    -

    +

    {{ "passwordManager" | i18n }}

    -

    +

    {{ "secretsManager" | i18n }}

    {{ additionalServiceAccountTotal(selectedPlan) | currency: "$" }}

    -

    +

    {{ "passwordManager" | i18n }}

    - + {{ "estimatedTax" | i18n }} @@ -986,14 +986,12 @@

    - + {{ "total" | i18n }} {{ total - calculateTotalAppliedDiscount(total) | currency: "USD" : "$" }} - - / {{ selectedPlanInterval | i18n }} + / {{ selectedPlanInterval | i18n }}

    diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index b0bdf31076b..0fd7746fc9d 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -31,7 +31,9 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlanInterval, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; @@ -149,6 +151,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { protected estimatedTax: number = 0; private _productTier = ProductTierType.Free; + private _familyPlan: PlanType; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @@ -247,6 +250,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private subscriberBillingClient: SubscriberBillingClient, private taxClient: TaxClient, private organizationWarningsService: OrganizationWarningsService, + private configService: ConfigService, ) {} async ngOnInit(): Promise { @@ -296,10 +300,16 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } } + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + this._familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) { const upgradedPlan = this.passwordManagerPlans.find((plan) => this.currentPlan.productTier === ProductTierType.Free - ? plan.type === PlanType.FamiliesAnnually + ? plan.type === this._familyPlan : plan.upgradeSortOrder == this.currentPlan.upgradeSortOrder + 1, ); @@ -544,9 +554,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } if (this.acceptingSponsorship) { - const familyPlan = this.passwordManagerPlans.find( - (plan) => plan.type === PlanType.FamiliesAnnually, - ); + const familyPlan = this.passwordManagerPlans.find((plan) => plan.type === this._familyPlan); this.discount = familyPlan.PasswordManager.basePrice; return [familyPlan]; } @@ -562,6 +570,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { plan.productTier === ProductTierType.TeamsStarter || (this.selectedInterval === PlanInterval.Annually && plan.isAnnual) || (this.selectedInterval === PlanInterval.Monthly && !plan.isAnnual)) && + (plan.productTier !== ProductTierType.Families || plan.type === this._familyPlan) && (!this.currentPlan || this.currentPlan.upgradeSortOrder < plan.upgradeSortOrder) && this.planIsEnabled(plan), ); @@ -795,7 +804,6 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { : this.i18nService.t("organizationUpgraded"), }); - await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); if (!this.acceptingSponsorship && !this.isInTrialFlow) { @@ -927,7 +935,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) { const upgradedPlan = this.passwordManagerPlans.find((plan) => { if (this.currentPlan.productTier === ProductTierType.Free) { - return plan.type === PlanType.FamiliesAnnually; + return plan.type === this._familyPlan; } if ( @@ -1025,6 +1033,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { const getPlanFromLegacyEnum = (planType: PlanType): OrganizationSubscriptionPlan => { switch (planType) { case PlanType.FamiliesAnnually: + case PlanType.FamiliesAnnually2025: return { tier: "families", cadence: "annually" }; case PlanType.TeamsMonthly: return { tier: "teams", cadence: "monthly" }; diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 0fa0b59b3cd..561a3e03deb 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -36,8 +36,10 @@ import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/commo import { BillingResponse } from "@bitwarden/common/billing/models/response/billing.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -126,6 +128,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } private _productTier = ProductTierType.Free; + private _familyPlan: PlanType; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @@ -217,6 +220,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private accountService: AccountService, private subscriberBillingClient: SubscriberBillingClient, private taxClient: TaxClient, + private configService: ConfigService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -256,10 +260,16 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { } } + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + this._familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) { const upgradedPlan = this.passwordManagerPlans.find((plan) => this.currentPlan.productTier === ProductTierType.Free - ? plan.type === PlanType.FamiliesAnnually + ? plan.type === this._familyPlan : plan.upgradeSortOrder == this.currentPlan.upgradeSortOrder + 1, ); @@ -378,9 +388,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { get selectableProducts() { if (this.acceptingSponsorship) { - const familyPlan = this.passwordManagerPlans.find( - (plan) => plan.type === PlanType.FamiliesAnnually, - ); + const familyPlan = this.passwordManagerPlans.find((plan) => plan.type === this._familyPlan); this.discount = familyPlan.PasswordManager.basePrice; return [familyPlan]; } @@ -397,6 +405,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { plan.productTier === ProductTierType.TeamsStarter) && (!this.currentPlan || this.currentPlan.upgradeSortOrder < plan.upgradeSortOrder) && (!this.hasProvider || plan.productTier !== ProductTierType.TeamsStarter) && + (plan.productTier !== ProductTierType.Families || plan.type === this._familyPlan) && ((!this.isProviderQualifiedFor2020Plan() && this.planIsEnabled(plan)) || (this.isProviderQualifiedFor2020Plan() && Allowed2020PlansForLegacyProviders.includes(plan.type))), @@ -413,6 +422,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.passwordManagerPlans?.filter( (plan) => plan.productTier === selectedProductTierType && + (plan.productTier !== ProductTierType.Families || plan.type === this._familyPlan) && ((!this.isProviderQualifiedFor2020Plan() && this.planIsEnabled(plan)) || (this.isProviderQualifiedFor2020Plan() && Allowed2020PlansForLegacyProviders.includes(plan.type))), @@ -675,7 +685,6 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { }); } - await this.apiService.refreshIdentityToken(); await this.syncService.fullSync(true); if (!this.acceptingSponsorship && !this.isInTrialFlow) { @@ -714,6 +723,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private getPlanFromLegacyEnum(): OrganizationSubscriptionPlan { switch (this.formGroup.value.plan) { case PlanType.FamiliesAnnually: + case PlanType.FamiliesAnnually2025: return { tier: "families", cadence: "annually" }; case PlanType.TeamsMonthly: return { tier: "teams", cadence: "monthly" }; @@ -986,7 +996,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) { const upgradedPlan = this.passwordManagerPlans.find((plan) => { if (this.currentPlan.productTier === ProductTierType.Free) { - return plan.type === PlanType.FamiliesAnnually; + return plan.type === this._familyPlan; } if ( 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 db3dde217c7..0666cca2c4b 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 @@ -241,7 +241,7 @@
    -

    {{ "billingManagedByProvider" | i18n: userOrg.providerName }}

    +

    {{ "billingManagedByProvider" | i18n: userOrg.providerName }}

    {{ "billingContactProviderForAssistance" | 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 fc9f8b1d986..70e16ad3037 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 @@ -300,6 +300,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy return this.i18nService.t("subscriptionFreePlan", this.sub.seats.toString()); } else if ( this.sub.planType === PlanType.FamiliesAnnually || + this.sub.planType === PlanType.FamiliesAnnually2025 || this.sub.planType === PlanType.FamiliesAnnually2019 || this.sub.planType === PlanType.TeamsStarter2023 || this.sub.planType === PlanType.TeamsStarter diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html index 1c823ed76cc..d4828e359b9 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html @@ -130,7 +130,7 @@ {{ "licenseAndBillingManagementDesc" | i18n }} -

    +

    {{ "uploadLicense" | i18n }}

    -

    {{ "billingManagedByProvider" | i18n: providerName }}

    +

    {{ "billingManagedByProvider" | i18n: providerName }}

    {{ "billingContactProviderForAssistance" | i18n }}

    `, standalone: false, diff --git a/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts b/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts index 1bc08159cdf..1ba1536ff36 100644 --- a/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/add-account-credit-dialog.component.ts @@ -58,7 +58,7 @@ const positiveNumberValidator = template: ` - + {{ "addCredit" | i18n }}
    diff --git a/apps/web/src/app/billing/payment/components/change-payment-method-dialog.component.ts b/apps/web/src/app/billing/payment/components/change-payment-method-dialog.component.ts index 71d156ecb26..756f7281049 100644 --- a/apps/web/src/app/billing/payment/components/change-payment-method-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/change-payment-method-dialog.component.ts @@ -24,7 +24,7 @@ type DialogParams = { template: ` - + {{ "changePaymentMethod" | i18n }}
    diff --git a/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts b/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts index aa9d2830527..3ac7cbd8702 100644 --- a/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/edit-billing-address-dialog.component.ts @@ -41,7 +41,7 @@ type DialogResult = template: ` - + {{ "editBillingAddress" | i18n }}
    diff --git a/apps/web/src/app/billing/payment/components/require-payment-method-dialog.component.ts b/apps/web/src/app/billing/payment/components/require-payment-method-dialog.component.ts index 3afd76e86ce..81775c83b58 100644 --- a/apps/web/src/app/billing/payment/components/require-payment-method-dialog.component.ts +++ b/apps/web/src/app/billing/payment/components/require-payment-method-dialog.component.ts @@ -35,7 +35,7 @@ type DialogParams = { template: ` - + {{ "addPaymentMethod" | i18n }}
    diff --git a/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.spec.ts b/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.spec.ts new file mode 100644 index 00000000000..086c7504040 --- /dev/null +++ b/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.spec.ts @@ -0,0 +1,147 @@ +import { firstValueFrom } from "rxjs"; + +import { + FakeAccountService, + FakeStateProvider, + mockAccountServiceWith, +} from "@bitwarden/common/spec"; +import { newGuid } from "@bitwarden/guid"; +import { UserId } from "@bitwarden/user-core"; + +import { + PREMIUM_INTEREST_KEY, + WebPremiumInterestStateService, +} from "./web-premium-interest-state.service"; + +describe("WebPremiumInterestStateService", () => { + let service: WebPremiumInterestStateService; + let stateProvider: FakeStateProvider; + let accountService: FakeAccountService; + + const mockUserId = newGuid() as UserId; + const mockUserEmail = "user@example.com"; + + beforeEach(() => { + accountService = mockAccountServiceWith(mockUserId, { email: mockUserEmail }); + stateProvider = new FakeStateProvider(accountService); + service = new WebPremiumInterestStateService(stateProvider); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("getPremiumInterest", () => { + it("should throw an error when userId is not provided", async () => { + const promise = service.getPremiumInterest(null); + + await expect(promise).rejects.toThrow("UserId is required. Cannot get 'premiumInterest'."); + }); + + it("should return null when no value is set", async () => { + const result = await service.getPremiumInterest(mockUserId); + + expect(result).toBeNull(); + }); + + it("should return true when value is set to true", async () => { + await stateProvider.setUserState(PREMIUM_INTEREST_KEY, true, mockUserId); + + const result = await service.getPremiumInterest(mockUserId); + + expect(result).toBe(true); + }); + + it("should return false when value is set to false", async () => { + await stateProvider.setUserState(PREMIUM_INTEREST_KEY, false, mockUserId); + + const result = await service.getPremiumInterest(mockUserId); + + expect(result).toBe(false); + }); + + it("should use getUserState$ to retrieve the value", async () => { + const getUserStateSpy = jest.spyOn(stateProvider, "getUserState$"); + await stateProvider.setUserState(PREMIUM_INTEREST_KEY, true, mockUserId); + + await service.getPremiumInterest(mockUserId); + + expect(getUserStateSpy).toHaveBeenCalledWith(PREMIUM_INTEREST_KEY, mockUserId); + }); + }); + + describe("setPremiumInterest", () => { + it("should throw an error when userId is not provided", async () => { + const promise = service.setPremiumInterest(null, true); + + await expect(promise).rejects.toThrow("UserId is required. Cannot set 'premiumInterest'."); + }); + + it("should set the value to true", async () => { + await service.setPremiumInterest(mockUserId, true); + + const result = await firstValueFrom( + stateProvider.getUserState$(PREMIUM_INTEREST_KEY, mockUserId), + ); + + expect(result).toBe(true); + }); + + it("should set the value to false", async () => { + await service.setPremiumInterest(mockUserId, false); + + const result = await firstValueFrom( + stateProvider.getUserState$(PREMIUM_INTEREST_KEY, mockUserId), + ); + + expect(result).toBe(false); + }); + + it("should update an existing value", async () => { + await service.setPremiumInterest(mockUserId, true); + await service.setPremiumInterest(mockUserId, false); + + const result = await firstValueFrom( + stateProvider.getUserState$(PREMIUM_INTEREST_KEY, mockUserId), + ); + + expect(result).toBe(false); + }); + + it("should use setUserState to store the value", async () => { + const setUserStateSpy = jest.spyOn(stateProvider, "setUserState"); + + await service.setPremiumInterest(mockUserId, true); + + expect(setUserStateSpy).toHaveBeenCalledWith(PREMIUM_INTEREST_KEY, true, mockUserId); + }); + }); + + describe("clearPremiumInterest", () => { + it("should throw an error when userId is not provided", async () => { + const promise = service.clearPremiumInterest(null); + + await expect(promise).rejects.toThrow("UserId is required. Cannot clear 'premiumInterest'."); + }); + + it("should clear the value by setting it to null", async () => { + await service.setPremiumInterest(mockUserId, true); + await service.clearPremiumInterest(mockUserId); + + const result = await firstValueFrom( + stateProvider.getUserState$(PREMIUM_INTEREST_KEY, mockUserId), + ); + + expect(result).toBeNull(); + }); + + it("should use setUserState with null to clear the value", async () => { + const setUserStateSpy = jest.spyOn(stateProvider, "setUserState"); + await service.setPremiumInterest(mockUserId, true); + + await service.clearPremiumInterest(mockUserId); + + expect(setUserStateSpy).toHaveBeenCalledWith(PREMIUM_INTEREST_KEY, null, mockUserId); + }); + }); +}); diff --git a/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.ts b/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.ts new file mode 100644 index 00000000000..f66fba559f4 --- /dev/null +++ b/apps/web/src/app/billing/services/premium-interest/web-premium-interest-state.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction"; +import { BILLING_MEMORY, StateProvider, UserKeyDefinition } from "@bitwarden/state"; +import { UserId } from "@bitwarden/user-core"; + +export const PREMIUM_INTEREST_KEY = new UserKeyDefinition( + BILLING_MEMORY, + "premiumInterest", + { + deserializer: (value: boolean) => value, + clearOn: ["lock", "logout"], + }, +); + +@Injectable() +export class WebPremiumInterestStateService implements PremiumInterestStateService { + constructor(private stateProvider: StateProvider) {} + + async getPremiumInterest(userId: UserId): Promise { + if (!userId) { + throw new Error("UserId is required. Cannot get 'premiumInterest'."); + } + + return await firstValueFrom(this.stateProvider.getUserState$(PREMIUM_INTEREST_KEY, userId)); + } + + async setPremiumInterest(userId: UserId, premiumInterest: boolean): Promise { + if (!userId) { + throw new Error("UserId is required. Cannot set 'premiumInterest'."); + } + + await this.stateProvider.setUserState(PREMIUM_INTEREST_KEY, premiumInterest, userId); + } + + async clearPremiumInterest(userId: UserId): Promise { + if (!userId) { + throw new Error("UserId is required. Cannot clear 'premiumInterest'."); + } + + await this.stateProvider.setUserState(PREMIUM_INTEREST_KEY, null, userId); + } +} diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index f7655ba0c6e..a2eb7cd98f2 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -226,7 +226,7 @@ export class StripeService { base: { color: null, fontFamily: - 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, ' + + 'Inter, "Helvetica Neue", Helvetica, Arial, sans-serif, ' + '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', fontSize: "16px", fontSmoothing: "antialiased", diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.html b/apps/web/src/app/billing/shared/offboarding-survey.component.html index 3fcbd39d8d4..b69565d95fa 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.html +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.html @@ -1,6 +1,6 @@ - + {{ "cancelSubscription" | i18n }}
    diff --git a/apps/web/src/app/billing/shared/plan-card/plan-card.component.html b/apps/web/src/app/billing/shared/plan-card/plan-card.component.html index af228842720..6f19facb0f5 100644 --- a/apps/web/src/app/billing/shared/plan-card/plan-card.component.html +++ b/apps/web/src/app/billing/shared/plan-card/plan-card.component.html @@ -11,7 +11,7 @@
    @if (isRecommended) {

    {{ plan().title }}

    - {{ plan().costPerMember | currency: "$" }} + {{ plan().costPerMember | currency: "$" }} /{{ "monthPerMember" | i18n }}
    diff --git a/apps/web/src/app/billing/shared/pricing-summary/pricing-summary.component.html b/apps/web/src/app/billing/shared/pricing-summary/pricing-summary.component.html index 428d6b7f04e..fdfff31da0f 100644 --- a/apps/web/src/app/billing/shared/pricing-summary/pricing-summary.component.html +++ b/apps/web/src/app/billing/shared/pricing-summary/pricing-summary.component.html @@ -1,7 +1,7 @@

    - {{ "total" | i18n }}: {{ summaryData.total - summaryData.totalAppliedDiscount | currency: "USD" : "$" }} USD @@ -37,7 +37,7 @@ -

    {{ "passwordManager" | i18n }}

    +

    {{ "passwordManager" | i18n }}

    @@ -137,7 +137,7 @@ -

    {{ "secretsManager" | i18n }}

    +

    {{ "secretsManager" | i18n }}

    @@ -236,7 +236,7 @@

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

    @@ -247,10 +247,10 @@

    - {{ "total" | i18n }} + {{ "total" | i18n }} {{ summaryData.total - summaryData.totalAppliedDiscount | currency: "USD" : "$" }} - / {{ summaryData.selectedPlanInterval | i18n }} diff --git a/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html b/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html index 1b416eae1bc..b3162507b9a 100644 --- a/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html +++ b/apps/web/src/app/billing/shared/trial-payment-dialog/trial-payment-dialog.component.html @@ -1,5 +1,5 @@ - + {{ "subscribetoEnterprise" | i18n: currentPlanName }} diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 19fa023a5b2..ef34584633b 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -251,7 +251,7 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { this.loading = true; let trialInitiationPath: InitiationPath = "Password Manager trial from marketing website"; let plan: PlanInformation = { - type: this.getPlanType(), + type: await this.getPlanType(), passwordManagerSeats: 1, }; @@ -293,14 +293,21 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { this.verticalStepper.previous(); } - getPlanType() { + async getPlanType() { + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + const familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; + switch (this.productTier) { case ProductTierType.Teams: return PlanType.TeamsAnnually; case ProductTierType.Enterprise: return PlanType.EnterpriseAnnually; case ProductTierType.Families: - return PlanType.FamiliesAnnually; + return familyPlan; case ProductTierType.Free: return PlanType.Free; default: diff --git a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html index 764a417f531..237fb381400 100644 --- a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html +++ b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.html @@ -9,7 +9,7 @@

  • {{ "trialConfirmationEmail" | i18n }} - {{ email }}{{ email }}.

  • diff --git a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html index 51b7f0c7117..e3f7b68bf95 100644 --- a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html +++ b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.component.html @@ -6,7 +6,7 @@
    -

    {{ "billingPlanLabel" | i18n }}

    +

    {{ "billingPlanLabel" | i18n }}

    @@ -32,7 +32,7 @@
    -

    {{ "paymentType" | i18n }}

    +

    {{ "paymentType" | i18n }}

    diff --git a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts index 9e4f45ede92..0888ef07afc 100644 --- a/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts +++ b/apps/web/src/app/billing/trial-initiation/trial-billing-step/trial-billing-step.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { firstValueFrom, from, map, shareReplay } from "rxjs"; +import { combineLatestWith, firstValueFrom, from, map, shareReplay } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; @@ -10,6 +10,8 @@ import { SubscriptionInformation, } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { TaxClient } from "@bitwarden/web-vault/app/billing/clients"; import { BillingAddressControls, @@ -62,6 +64,7 @@ export class TrialBillingStepService { private apiService: ApiService, private organizationBillingService: OrganizationBillingServiceAbstraction, private taxClient: TaxClient, + private configService: ConfigService, ) {} private plans$ = from(this.apiService.getPlans()).pipe( @@ -70,10 +73,17 @@ export class TrialBillingStepService { getPrices$ = (product: Product, tier: Tier) => this.plans$.pipe( - map((plans) => { + combineLatestWith(this.configService.getFeatureFlag$(FeatureFlag.PM26462_Milestone_3)), + map(([plans, milestone3FeatureEnabled]) => { switch (tier) { case "families": { - const annually = plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually); + const annually = plans.data.find( + (plan) => + plan.type === + (milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025), + ); return { annually: annually!.PasswordManager.basePrice, }; @@ -149,9 +159,15 @@ export class TrialBillingStepService { ): Promise => { const getPlanType = async (tier: Tier, cadence: Cadence) => { const plans = await firstValueFrom(this.plans$); + const milestone3FeatureEnabled = await this.configService.getFeatureFlag( + FeatureFlag.PM26462_Milestone_3, + ); + const familyPlan = milestone3FeatureEnabled + ? PlanType.FamiliesAnnually + : PlanType.FamiliesAnnually2025; switch (tier) { case "families": - return plans.data.find((plan) => plan.type === PlanType.FamiliesAnnually)!.type; + return plans.data.find((plan) => plan.type === familyPlan)!.type; case "teams": return plans.data.find( (plan) => diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html index 5d7d3c62d2f..bd1a9dc59a7 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.html @@ -11,7 +11,7 @@ [attr.aria-expanded]="selected" > @@ -30,7 +30,7 @@ diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 29b84ddc382..bf741132b00 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -9,11 +9,16 @@ import { DefaultCollectionAdminService, OrganizationUserApiService, CollectionService, + AutomaticUserConfirmationService, + DefaultAutomaticUserConfirmationService, + OrganizationUserService, + DefaultOrganizationUserService, } from "@bitwarden/admin-console/common"; import { DefaultDeviceManagementComponentService } from "@bitwarden/angular/auth/device-management/default-device-management-component.service"; import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction"; import { ChangePasswordService } from "@bitwarden/angular/auth/password-management/change-password"; import { SetInitialPasswordService } from "@bitwarden/angular/auth/password-management/set-initial-password/set-initial-password.service.abstraction"; +import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { CLIENT_TYPE, @@ -43,7 +48,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + InternalOrganizationServiceAbstraction, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService, @@ -55,6 +63,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; @@ -78,6 +87,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { SdkClientFactory } from "@bitwarden/common/platform/abstractions/sdk/sdk-client-factory"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service"; +import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; import { IpcService } from "@bitwarden/common/platform/ipc"; // eslint-disable-next-line no-restricted-imports -- Needed for DI import { @@ -94,6 +104,7 @@ import { NoopSdkLoadService } from "@bitwarden/common/platform/services/sdk/noop import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state"; import { WindowStorageService } from "@bitwarden/common/platform/storage/window-storage.service"; +import { SyncService } from "@bitwarden/common/platform/sync/sync.service"; import { DefaultThemeStateService, ThemeStateService, @@ -127,6 +138,7 @@ import { WebSetInitialPasswordService, } from "../auth"; import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service"; +import { WebPremiumInterestStateService } from "../billing/services/premium-interest/web-premium-interest-state.service"; import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; import { WebFileDownloadService } from "../core/web-file-download.service"; @@ -139,6 +151,7 @@ import { WebEnvironmentService } from "../platform/web-environment.service"; import { WebMigrationRunner } from "../platform/web-migration-runner"; import { WebSdkLoadService } from "../platform/web-sdk-load.service"; import { WebStorageServiceProvider } from "../platform/web-storage-service.provider"; +import { WebSystemService } from "../platform/web-system.service"; import { EventService } from "./event.service"; import { InitService } from "./init.service"; @@ -332,6 +345,29 @@ const safeProviders: SafeProvider[] = [ OrganizationService, ], }), + safeProvider({ + provide: OrganizationUserService, + useClass: DefaultOrganizationUserService, + deps: [ + KeyServiceAbstraction, + EncryptService, + OrganizationUserApiService, + AccountService, + I18nServiceAbstraction, + ], + }), + safeProvider({ + provide: AutomaticUserConfirmationService, + useClass: DefaultAutomaticUserConfirmationService, + deps: [ + ConfigService, + ApiService, + OrganizationUserService, + StateProvider, + InternalOrganizationServiceAbstraction, + OrganizationUserApiService, + ], + }), safeProvider({ provide: SdkLoadService, useClass: flagEnabled("sdk") ? WebSdkLoadService : NoopSdkLoadService, @@ -408,7 +444,26 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: PremiumUpgradePromptService, useClass: WebVaultPremiumUpgradePromptService, - deps: [DialogService, Router], + deps: [ + DialogService, + ConfigService, + AccountService, + ApiService, + SyncService, + BillingAccountProfileStateService, + PlatformUtilsService, + Router, + ], + }), + safeProvider({ + provide: PremiumInterestStateService, + useClass: WebPremiumInterestStateService, + deps: [StateProvider], + }), + safeProvider({ + provide: SystemService, + useClass: WebSystemService, + deps: [], }), ]; diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html index dab928e6ec3..f0318028e60 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html @@ -12,17 +12,15 @@
    -

    {{ title }}

    +

    {{ title }}

    {{ description }}

    - - {{ "premium" | i18n }} - {{ "upgrade" | i18n }} - + @if (requiresPremium) { + + } @else if (requiresUpgrade) { + + {{ "upgrade" | i18n }} + + } diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts index 565035c2c55..87c005ea46b 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts @@ -37,4 +37,8 @@ export class ReportCardComponent { protected get requiresPremium() { return this.variant == ReportVariant.RequiresPremium; } + + protected get requiresUpgrade() { + return this.variant == ReportVariant.RequiresUpgrade; + } } diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.stories.ts b/apps/web/src/app/dirt/reports/shared/report-card/report-card.stories.ts index 50798fea6e1..93ea79c8418 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.stories.ts +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.stories.ts @@ -1,14 +1,20 @@ import { importProvidersFrom } from "@angular/core"; import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; +import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { of } from "rxjs"; import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { BadgeModule, BaseCardComponent, - IconModule, CardContentComponent, + I18nMockService, + IconModule, } from "@bitwarden/components"; import { PreloadedEnglishI18nModule } from "../../../../core/tests"; @@ -30,6 +36,37 @@ export default { PremiumBadgeComponent, BaseCardComponent, ], + providers: [ + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + }), + }, + }, + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + premium: "Premium", + upgrade: "Upgrade", + }); + }, + }, + { + provide: BillingAccountProfileStateService, + useValue: { + hasPremiumFromAnySource$: () => of(false), + }, + }, + { + provide: PremiumUpgradePromptService, + useValue: { + promptForPremium: (orgId?: string) => {}, + }, + }, + ], }), applicationConfig({ providers: [importProvidersFrom(PreloadedEnglishI18nModule)], diff --git a/apps/web/src/app/dirt/reports/shared/report-list/report-list.stories.ts b/apps/web/src/app/dirt/reports/shared/report-list/report-list.stories.ts index 5a89eeff803..5a95e332816 100644 --- a/apps/web/src/app/dirt/reports/shared/report-list/report-list.stories.ts +++ b/apps/web/src/app/dirt/reports/shared/report-list/report-list.stories.ts @@ -1,9 +1,13 @@ import { importProvidersFrom } from "@angular/core"; import { RouterTestingModule } from "@angular/router/testing"; -import { Meta, StoryObj, applicationConfig, moduleMetadata } from "@storybook/angular"; +import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { of } from "rxjs"; import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { BadgeModule, BaseCardComponent, @@ -33,6 +37,28 @@ export default { BaseCardComponent, ], declarations: [ReportCardComponent], + providers: [ + { + provide: AccountService, + useValue: { + activeAccount$: of({ + id: "123", + }), + }, + }, + { + provide: BillingAccountProfileStateService, + useValue: { + hasPremiumFromAnySource$: () => of(false), + }, + }, + { + provide: PremiumUpgradePromptService, + useValue: { + promptForPremium: (orgId?: string) => {}, + }, + }, + ], }), applicationConfig({ providers: [importProvidersFrom(PreloadedEnglishI18nModule)], diff --git a/apps/web/src/app/dirt/reports/shared/reports-shared.module.ts b/apps/web/src/app/dirt/reports/shared/reports-shared.module.ts index 59e59a6a500..940a2d4e3a5 100644 --- a/apps/web/src/app/dirt/reports/shared/reports-shared.module.ts +++ b/apps/web/src/app/dirt/reports/shared/reports-shared.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { BaseCardComponent, CardContentComponent } from "@bitwarden/components"; import { SharedModule } from "../../../shared/shared.module"; @@ -9,7 +10,13 @@ import { ReportCardComponent } from "./report-card/report-card.component"; import { ReportListComponent } from "./report-list/report-list.component"; @NgModule({ - imports: [CommonModule, SharedModule, BaseCardComponent, CardContentComponent], + imports: [ + CommonModule, + SharedModule, + BaseCardComponent, + CardContentComponent, + PremiumBadgeComponent, + ], declarations: [ReportCardComponent, ReportListComponent], exports: [ReportCardComponent, ReportListComponent], }) diff --git a/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts b/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts index 95e54e16464..965d866c91c 100644 --- a/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts +++ b/apps/web/src/app/key-management/key-rotation/request/master-password-unlock-data.request.ts @@ -9,6 +9,9 @@ export class MasterPasswordUnlockDataRequest { email: string; masterKeyAuthenticationHash: string; + /** + * Also known as masterKeyWrappedUserKey in other parts of the codebase + */ masterKeyEncryptedUserKey: string; masterPasswordHint?: string; @@ -17,7 +20,7 @@ export class MasterPasswordUnlockDataRequest { kdfConfig: KdfConfig, email: string, masterKeyAuthenticationHash: string, - masterKeyEncryptedUserKey: string, + masterKeyWrappedUserKey: string, masterPasswordHash?: string, ) { this.kdfType = kdfConfig.kdfType; @@ -29,7 +32,7 @@ export class MasterPasswordUnlockDataRequest { this.email = email; this.masterKeyAuthenticationHash = masterKeyAuthenticationHash; - this.masterKeyEncryptedUserKey = masterKeyEncryptedUserKey; + this.masterKeyEncryptedUserKey = masterKeyWrappedUserKey; this.masterPasswordHint = masterPasswordHash; } } diff --git a/apps/web/src/app/key-management/services/web-process-reload.service.ts b/apps/web/src/app/key-management/services/web-process-reload.service.ts index c542c97c0e0..6f055cd990c 100644 --- a/apps/web/src/app/key-management/services/web-process-reload.service.ts +++ b/apps/web/src/app/key-management/services/web-process-reload.service.ts @@ -1,10 +1,9 @@ -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; export class WebProcessReloadService implements ProcessReloadServiceAbstraction { constructor(private window: Window) {} - async startProcessReload(authService: AuthService): Promise { + async startProcessReload(): Promise { this.window.location.reload(); } diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index 4b833e771dd..992ba147075 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -12,7 +12,7 @@

    diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html index d39156ef4a2..9c8f2125614 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.html @@ -29,7 +29,7 @@ [href]="more.marketingRoute.route" target="_blank" rel="noreferrer" - class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-bold !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline" + class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline" >
    @@ -47,7 +47,7 @@ *ngIf="!more.marketingRoute.external" [routerLink]="more.marketingRoute.route" rel="noreferrer" - class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-bold !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline" + class="tw-flex tw-px-3 tw-py-2 tw-rounded-md tw-font-medium !tw-text-alt2 !tw-no-underline hover:tw-bg-hover-contrast [&>:not(.bwi)]:hover:tw-underline" >
    diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html index aa853796971..f2154ec74a3 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.html @@ -14,7 +14,7 @@ [routerLink]="product.appRoute" [ngClass]=" product.isActive - ? 'tw-bg-primary-600 tw-font-bold !tw-text-contrast tw-ring-offset-2 hover:tw-bg-primary-600' + ? 'tw-bg-primary-600 tw-font-medium !tw-text-contrast tw-ring-offset-2 hover:tw-bg-primary-600' : '' " class="tw-group/product-link tw-flex tw-h-24 tw-w-28 tw-flex-col tw-items-center tw-justify-center tw-rounded tw-p-1 tw-text-primary-600 tw-outline-none hover:tw-bg-background-alt hover:tw-text-primary-700 hover:tw-no-underline focus-visible:!tw-ring-2 focus-visible:!tw-ring-primary-700" diff --git a/apps/web/src/app/layouts/user-layout.component.html b/apps/web/src/app/layouts/user-layout.component.html index 530d4caca03..23f22d263cf 100644 --- a/apps/web/src/app/layouts/user-layout.component.html +++ b/apps/web/src/app/layouts/user-layout.component.html @@ -20,10 +20,12 @@ *ngIf="showSubscription$ | async" > - + @if (showEmergencyAccess()) { + + } diff --git a/apps/web/src/app/layouts/user-layout.component.ts b/apps/web/src/app/layouts/user-layout.component.ts index 9642803ef30..52e5b65a2e8 100644 --- a/apps/web/src/app/layouts/user-layout.component.ts +++ b/apps/web/src/app/layouts/user-layout.component.ts @@ -1,14 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; +import { Component, OnInit, Signal } from "@angular/core"; +import { toSignal } from "@angular/core/rxjs-interop"; import { RouterModule } from "@angular/router"; -import { Observable, switchMap } from "rxjs"; +import { combineLatest, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { PasswordManagerLogo } from "@bitwarden/assets/svg"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; +import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { IconModule } from "@bitwarden/components"; @@ -32,6 +38,7 @@ import { WebLayoutModule } from "./web-layout.module"; }) export class UserLayoutComponent implements OnInit { protected readonly logo = PasswordManagerLogo; + protected readonly showEmergencyAccess: Signal; protected hasFamilySponsorshipAvailable$: Observable; protected showSponsoredFamilies$: Observable; protected showSubscription$: Observable; @@ -40,12 +47,33 @@ export class UserLayoutComponent implements OnInit { private syncService: SyncService, private billingAccountProfileStateService: BillingAccountProfileStateService, private accountService: AccountService, + private policyService: PolicyService, + private configService: ConfigService, ) { this.showSubscription$ = this.accountService.activeAccount$.pipe( switchMap((account) => this.billingAccountProfileStateService.canViewSubscription$(account.id), ), ); + + this.showEmergencyAccess = toSignal( + combineLatest([ + this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm), + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => + this.policyService.policyAppliesToUser$(PolicyType.AutoConfirm, userId), + ), + ), + ]).pipe( + map(([enabled, policyAppliesToUser]) => { + if (!enabled || !policyAppliesToUser) { + return true; + } + return false; + }), + ), + ); } async ngOnInit() { diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 319adb1d8c6..4db6e50bc6d 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -47,11 +47,14 @@ import { TwoFactorAuthGuard, NewDeviceVerificationComponent, } from "@bitwarden/auth/angular"; +import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; import { LockComponent } from "@bitwarden/key-management-ui"; +import { premiumInterestRedirectGuard } from "@bitwarden/web-vault/app/vault/guards/premium-interest-redirect/premium-interest-redirect.guard"; import { flagEnabled, Flags } from "../utils/flags"; +import { organizationPolicyGuard } from "./admin-console/organizations/guards/org-policy.guard"; import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component"; import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; @@ -628,7 +631,7 @@ const routes: Routes = [ children: [ { path: "vault", - canActivate: [setupExtensionRedirectGuard], + canActivate: [premiumInterestRedirectGuard, setupExtensionRedirectGuard], loadChildren: () => VaultModule, }, { @@ -687,11 +690,13 @@ const routes: Routes = [ { path: "", component: EmergencyAccessComponent, + canActivate: [organizationPolicyGuard(canAccessEmergencyAccess)], data: { titleId: "emergencyAccess" } satisfies RouteDataProperties, }, { path: ":id", component: EmergencyAccessViewComponent, + canActivate: [organizationPolicyGuard(canAccessEmergencyAccess)], data: { titleId: "emergencyAccess" } satisfies RouteDataProperties, }, ], diff --git a/apps/web/src/app/platform/web-system.service.ts b/apps/web/src/app/platform/web-system.service.ts new file mode 100644 index 00000000000..b614d0f9245 --- /dev/null +++ b/apps/web/src/app/platform/web-system.service.ts @@ -0,0 +1,10 @@ +import { SystemService } from "@bitwarden/common/platform/abstractions/system.service"; + +/** + * Web implementation of SystemService. + * The implementation is NOOP since these functions are not supported on web. + */ +export class WebSystemService extends SystemService { + async clearClipboard(clipboardValue: string, timeoutMs?: number): Promise {} + async clearPendingClipboard(): Promise {} +} diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index b79f50311ed..b8538606aec 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -21,7 +21,7 @@
    {{ "filters" | i18n }} diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html index 09bd38c8517..038c258d4b6 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html @@ -6,7 +6,7 @@ >
    -

    +

    {{ "setupExtensionPageTitle" | i18n }}

    diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts index e45a82d82ba..6716cde629a 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.spec.ts @@ -6,11 +6,14 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { DialogRef, DIALOG_DATA, DialogService, ToastService } from "@bitwarden/components"; @@ -73,6 +76,7 @@ describe("VaultItemDialogComponent", () => { { provide: LogService, useValue: {} }, { provide: CipherService, useValue: {} }, { provide: AccountService, useValue: { activeAccount$: { pipe: () => ({}) } } }, + { provide: ConfigService, useValue: { getFeatureFlag: () => Promise.resolve(false) } }, { provide: Router, useValue: {} }, { provide: ActivatedRoute, useValue: {} }, { @@ -84,6 +88,8 @@ describe("VaultItemDialogComponent", () => { { provide: ApiService, useValue: {} }, { provide: EventCollectionService, useValue: {} }, { provide: RoutedVaultFilterService, useValue: {} }, + { provide: SyncService, useValue: {} }, + { provide: PlatformUtilsService, useValue: {} }, ], }).compileComponents(); diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index d6b5fafe6ec..cb2af9a64e5 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -12,7 +12,7 @@ (change)="$event ? toggleAll() : null" [checked]="selection.hasValue() && isAllSelected" /> -

    {{ "filters" | i18n }} 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 b9a3bbfdd19..4c23119f1eb 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -9,6 +9,7 @@ import { lastValueFrom, Observable, Subject, + zip, } from "rxjs"; import { concatMap, @@ -25,6 +26,7 @@ import { } from "rxjs/operators"; import { + AutomaticUserConfirmationService, CollectionData, CollectionDetailsResponse, CollectionService, @@ -54,7 +56,9 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -65,6 +69,7 @@ import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -101,6 +106,11 @@ import { getNestedCollectionTree, getFlatCollectionTree, } from "../../admin-console/organizations/collections"; +import { + AutoConfirmPolicy, + AutoConfirmPolicyDialogComponent, + PolicyEditDialogResult, +} from "../../admin-console/organizations/policies"; import { CollectionDialogAction, CollectionDialogTabType, @@ -212,6 +222,8 @@ export class VaultComponent implements OnInit, OnDestr private destroy$ = new Subject(); private vaultItemDialogRef?: DialogRef | undefined; + private autoConfirmDialogRef?: DialogRef | undefined; + protected showAddCipherBtn: boolean = false; organizations$ = this.accountService.activeAccount$ @@ -326,6 +338,9 @@ export class VaultComponent implements OnInit, OnDestr private organizationWarningsService: OrganizationWarningsService, private policyService: PolicyService, private unifiedUpgradePromptService: UnifiedUpgradePromptService, + private premiumUpgradePromptService: PremiumUpgradePromptService, + private autoConfirmService: AutomaticUserConfirmationService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -627,6 +642,8 @@ export class VaultComponent implements OnInit, OnDestr }, ); void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); + + this.setupAutoConfirm(); } ngOnDestroy() { @@ -867,7 +884,7 @@ export class VaultComponent implements OnInit, OnDestr } if (cipher.organizationId == null && !this.canAccessPremium) { - this.messagingService.send("premiumRequired"); + await this.premiumUpgradePromptService.promptForPremium(); return; } else if (cipher.organizationId != null) { const org = await firstValueFrom( @@ -1545,6 +1562,72 @@ export class VaultComponent implements OnInit, OnDestr const cipherView = await this.cipherService.decrypt(_cipher, activeUserId); return cipherView.login?.password; } + + private async openAutoConfirmFeatureDialog(organization: Organization) { + if (this.autoConfirmDialogRef) { + return; + } + + this.autoConfirmDialogRef = AutoConfirmPolicyDialogComponent.open(this.dialogService, { + data: { + policy: new AutoConfirmPolicy(), + organizationId: organization.id, + firstTimeDialog: true, + }, + }); + + await lastValueFrom(this.autoConfirmDialogRef.closed); + this.autoConfirmDialogRef = undefined; + } + + private setupAutoConfirm() { + // if the policy is enabled, then the user may only belong to one organization at most. + const organization$ = this.organizations$.pipe(map((organizations) => organizations[0])); + + const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm); + + const autoConfirmState$ = this.userId$.pipe( + switchMap((userId) => this.autoConfirmService.configuration$(userId)), + ); + + const policyEnabled$ = combineLatest([ + this.userId$.pipe( + switchMap((userId) => this.policyService.policies$(userId)), + map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm && p.enabled)), + ), + organization$, + ]).pipe( + map( + ([policy, organization]) => (policy && policy.organizationId === organization?.id) ?? false, + ), + ); + + zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$]) + .pipe( + first(), + switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => { + const showDialog = + flagEnabled && + !policyEnabled && + autoConfirmState.showSetupDialog && + !!organization && + (organization.canManageUsers || organization.canManagePolicies); + + if (showDialog) { + await this.openAutoConfirmFeatureDialog(organization); + + await this.autoConfirmService.upsert(userId, { + ...autoConfirmState, + showSetupDialog: false, + }); + } + }), + takeUntil(this.destroy$), + ) + .subscribe({ + error: (err: unknown) => this.logService.error("Failed to update auto-confirm state", err), + }); + } } /** diff --git a/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.spec.ts b/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.spec.ts index 1f34b823aec..ad16baee42e 100644 --- a/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.spec.ts +++ b/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.spec.ts @@ -2,8 +2,19 @@ import { TestBed } from "@angular/core/testing"; import { Router } from "@angular/router"; import { lastValueFrom, of } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +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/platform/sync"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogRef, DialogService } from "@bitwarden/components"; +import { + UnifiedUpgradeDialogComponent, + UnifiedUpgradeDialogStatus, +} from "@bitwarden/web-vault/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component"; import { VaultItemDialogResult } from "../components/vault-item-dialog/vault-item-dialog.component"; @@ -13,13 +24,27 @@ describe("WebVaultPremiumUpgradePromptService", () => { let service: WebVaultPremiumUpgradePromptService; let dialogServiceMock: jest.Mocked; let routerMock: jest.Mocked; - let dialogRefMock: jest.Mocked>; + let dialogRefMock: jest.Mocked; + let configServiceMock: jest.Mocked; + let accountServiceMock: jest.Mocked; + let apiServiceMock: jest.Mocked; + let syncServiceMock: jest.Mocked; + let billingAccountProfileServiceMock: jest.Mocked; + let platformUtilsServiceMock: jest.Mocked; beforeEach(() => { dialogServiceMock = { openSimpleDialog: jest.fn(), } as unknown as jest.Mocked; + configServiceMock = { + getFeatureFlag: jest.fn().mockReturnValue(false), + } as unknown as jest.Mocked; + + accountServiceMock = { + activeAccount$: of({ id: "user-123" }), + } as unknown as jest.Mocked; + routerMock = { navigate: jest.fn(), } as unknown as jest.Mocked; @@ -28,12 +53,34 @@ describe("WebVaultPremiumUpgradePromptService", () => { close: jest.fn(), } as unknown as jest.Mocked>; + apiServiceMock = { + refreshIdentityToken: jest.fn().mockReturnValue({}), + } as unknown as jest.Mocked; + + syncServiceMock = { + fullSync: jest.fn(), + } as unknown as jest.Mocked; + + billingAccountProfileServiceMock = { + hasPremiumFromAnySource$: jest.fn().mockReturnValue(of(false)), + } as unknown as jest.Mocked; + + platformUtilsServiceMock = { + isSelfHost: jest.fn().mockReturnValue(false), + } as unknown as jest.Mocked; + TestBed.configureTestingModule({ providers: [ WebVaultPremiumUpgradePromptService, { provide: DialogService, useValue: dialogServiceMock }, { provide: Router, useValue: routerMock }, { provide: DialogRef, useValue: dialogRefMock }, + { provide: ConfigService, useValue: configServiceMock }, + { provide: AccountService, useValue: accountServiceMock }, + { provide: ApiService, useValue: apiServiceMock }, + { provide: SyncService, useValue: syncServiceMock }, + { provide: BillingAccountProfileStateService, useValue: billingAccountProfileServiceMock }, + { provide: PlatformUtilsService, useValue: platformUtilsServiceMock }, ], }); @@ -84,4 +131,144 @@ describe("WebVaultPremiumUpgradePromptService", () => { expect(routerMock.navigate).not.toHaveBeenCalled(); expect(dialogRefMock.close).not.toHaveBeenCalled(); }); + + describe("premium status check", () => { + it("should not prompt if user already has premium (feature flag off)", async () => { + configServiceMock.getFeatureFlag.mockReturnValue(Promise.resolve(false)); + billingAccountProfileServiceMock.hasPremiumFromAnySource$.mockReturnValue(of(true)); + + await service.promptForPremium(); + + expect(dialogServiceMock.openSimpleDialog).not.toHaveBeenCalled(); + expect(routerMock.navigate).not.toHaveBeenCalled(); + }); + + it("should not prompt if user already has premium (feature flag on)", async () => { + configServiceMock.getFeatureFlag.mockImplementation((flag: FeatureFlag) => { + if (flag === FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog) { + return Promise.resolve(true); + } + return Promise.resolve(false); + }); + billingAccountProfileServiceMock.hasPremiumFromAnySource$.mockReturnValue(of(true)); + + const unifiedDialogRefMock = { + closed: of({ status: UnifiedUpgradeDialogStatus.Closed }), + close: jest.fn(), + } as any; + jest.spyOn(UnifiedUpgradeDialogComponent, "open").mockReturnValue(unifiedDialogRefMock); + + await service.promptForPremium(); + + expect(UnifiedUpgradeDialogComponent.open).not.toHaveBeenCalled(); + expect(dialogServiceMock.openSimpleDialog).not.toHaveBeenCalled(); + expect(routerMock.navigate).not.toHaveBeenCalled(); + }); + }); + + describe("new premium upgrade dialog with post-upgrade actions", () => { + beforeEach(() => { + configServiceMock.getFeatureFlag.mockImplementation((flag: FeatureFlag) => { + if (flag === FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog) { + return Promise.resolve(true); + } + return Promise.resolve(false); + }); + }); + + describe("when self-hosted", () => { + beforeEach(() => { + platformUtilsServiceMock.isSelfHost.mockReturnValue(true); + }); + + it("should navigate to subscription page instead of opening dialog", async () => { + await service.promptForPremium(); + + expect(routerMock.navigate).toHaveBeenCalledWith(["settings/subscription/premium"]); + expect(dialogServiceMock.openSimpleDialog).not.toHaveBeenCalled(); + }); + }); + + describe("when not self-hosted", () => { + beforeEach(() => { + platformUtilsServiceMock.isSelfHost.mockReturnValue(false); + }); + + it("should full sync when user upgrades to premium", async () => { + const unifiedDialogRefMock = { + closed: of({ status: UnifiedUpgradeDialogStatus.UpgradedToPremium }), + close: jest.fn(), + } as any; + jest.spyOn(UnifiedUpgradeDialogComponent, "open").mockReturnValue(unifiedDialogRefMock); + + await service.promptForPremium(); + + expect(UnifiedUpgradeDialogComponent.open).toHaveBeenCalledWith(dialogServiceMock, { + data: { + account: { id: "user-123" }, + planSelectionStepTitleOverride: "upgradeYourPlan", + hideContinueWithoutUpgradingButton: true, + }, + }); + expect(syncServiceMock.fullSync).toHaveBeenCalledWith(true); + }); + + it("should full sync when user upgrades to families", async () => { + const unifiedDialogRefMock = { + closed: of({ status: UnifiedUpgradeDialogStatus.UpgradedToFamilies }), + close: jest.fn(), + } as any; + jest.spyOn(UnifiedUpgradeDialogComponent, "open").mockReturnValue(unifiedDialogRefMock); + + await service.promptForPremium(); + + expect(UnifiedUpgradeDialogComponent.open).toHaveBeenCalledWith(dialogServiceMock, { + data: { + account: { id: "user-123" }, + planSelectionStepTitleOverride: "upgradeYourPlan", + hideContinueWithoutUpgradingButton: true, + }, + }); + expect(syncServiceMock.fullSync).toHaveBeenCalledWith(true); + }); + + it("should not refresh or sync when user closes dialog without upgrading", async () => { + const unifiedDialogRefMock = { + closed: of({ status: UnifiedUpgradeDialogStatus.Closed }), + close: jest.fn(), + } as any; + jest.spyOn(UnifiedUpgradeDialogComponent, "open").mockReturnValue(unifiedDialogRefMock); + + await service.promptForPremium(); + + expect(UnifiedUpgradeDialogComponent.open).toHaveBeenCalledWith(dialogServiceMock, { + data: { + account: { id: "user-123" }, + planSelectionStepTitleOverride: "upgradeYourPlan", + hideContinueWithoutUpgradingButton: true, + }, + }); + expect(apiServiceMock.refreshIdentityToken).not.toHaveBeenCalled(); + expect(syncServiceMock.fullSync).not.toHaveBeenCalled(); + }); + + it("should not open new dialog if organizationId is provided", async () => { + const organizationId = "test-org-id" as OrganizationId; + dialogServiceMock.openSimpleDialog.mockReturnValue(lastValueFrom(of(true))); + + const openSpy = jest.spyOn(UnifiedUpgradeDialogComponent, "open"); + openSpy.mockClear(); + + await service.promptForPremium(organizationId); + + expect(openSpy).not.toHaveBeenCalled(); + expect(dialogServiceMock.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "upgradeOrganization" }, + content: { key: "upgradeOrganizationDesc" }, + acceptButtonText: { key: "upgradeOrganization" }, + type: "info", + }); + }); + }); + }); }); diff --git a/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.ts b/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.ts index 87fcdc345d8..c456cf6cc13 100644 --- a/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.ts +++ b/apps/web/src/app/vault/services/web-premium-upgrade-prompt.service.ts @@ -1,10 +1,21 @@ import { Injectable, Optional } from "@angular/core"; import { Router } from "@angular/router"; -import { Subject } from "rxjs"; +import { firstValueFrom, lastValueFrom, Subject } from "rxjs"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { DialogRef, DialogService } from "@bitwarden/components"; +import { + UnifiedUpgradeDialogComponent, + UnifiedUpgradeDialogStatus, +} from "@bitwarden/web-vault/app/billing/individual/upgrade/unified-upgrade-dialog/unified-upgrade-dialog.component"; import { VaultItemDialogResult } from "../components/vault-item-dialog/vault-item-dialog.component"; @@ -15,14 +26,44 @@ export class WebVaultPremiumUpgradePromptService implements PremiumUpgradePrompt constructor( private dialogService: DialogService, + private configService: ConfigService, + private accountService: AccountService, + private apiService: ApiService, + private syncService: SyncService, + private billingAccountProfileStateService: BillingAccountProfileStateService, + private platformUtilsService: PlatformUtilsService, private router: Router, @Optional() private dialog?: DialogRef, ) {} + private readonly subscriptionPageRoute = "settings/subscription/premium"; /** * Prompts the user for a premium upgrade. */ async promptForPremium(organizationId?: OrganizationId) { + const account = await firstValueFrom(this.accountService.activeAccount$); + if (!account) { + return; + } + const hasPremium = await firstValueFrom( + this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id), + ); + if (hasPremium) { + // Already has premium, don't prompt + return; + } + + const showNewDialog = await this.configService.getFeatureFlag( + FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog, + ); + + // Per conversation in PM-23713, retain the existing upgrade org flow for now, will be addressed + // as a part of https://bitwarden.atlassian.net/browse/PM-25507 + if (showNewDialog && !organizationId) { + await this.promptForPremiumVNext(account); + return; + } + let confirmed = false; let route: string[] | null = null; @@ -44,7 +85,7 @@ export class WebVaultPremiumUpgradePromptService implements PremiumUpgradePrompt type: "success", }); if (confirmed) { - route = ["settings/subscription/premium"]; + route = [this.subscriptionPageRoute]; } } @@ -57,4 +98,31 @@ export class WebVaultPremiumUpgradePromptService implements PremiumUpgradePrompt this.dialog.close(VaultItemDialogResult.PremiumUpgrade); } } + + private async promptForPremiumVNext(account: Account) { + await (this.platformUtilsService.isSelfHost() + ? this.redirectToSubscriptionPage() + : this.openUpgradeDialog(account)); + } + + private async redirectToSubscriptionPage() { + await this.router.navigate([this.subscriptionPageRoute]); + } + + private async openUpgradeDialog(account: Account) { + const dialogRef = UnifiedUpgradeDialogComponent.open(this.dialogService, { + data: { + account, + planSelectionStepTitleOverride: "upgradeYourPlan", + hideContinueWithoutUpgradingButton: true, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if ( + result?.status === UnifiedUpgradeDialogStatus.UpgradedToPremium || + result?.status === UnifiedUpgradeDialogStatus.UpgradedToFamilies + ) { + await this.syncService.fullSync(true); + } + } } diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index ae8f84715db..842bd8c0064 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -123,7 +123,7 @@ function displayHandoffMessage(client: string) { ? localeService.t("thisWindowWillCloseIn5Seconds") : localeService.t("youMayCloseThisWindow"); - h1.className = "tw-font-semibold"; + h1.className = "tw-font-medium"; p.className = "tw-mb-4"; content.appendChild(h1); diff --git a/apps/web/src/connectors/webauthn-fallback.html b/apps/web/src/connectors/webauthn-fallback.html index 43da5b1a485..ef85ce6f351 100644 --- a/apps/web/src/connectors/webauthn-fallback.html +++ b/apps/web/src/connectors/webauthn-fallback.html @@ -115,7 +115,7 @@
    diff --git a/apps/web/src/connectors/webauthn-mobile.html b/apps/web/src/connectors/webauthn-mobile.html index 06df8b012ab..0551d176eab 100644 --- a/apps/web/src/connectors/webauthn-mobile.html +++ b/apps/web/src/connectors/webauthn-mobile.html @@ -24,7 +24,7 @@
    diff --git a/apps/web/src/connectors/webauthn.html b/apps/web/src/connectors/webauthn.html index 27f143f90d3..358e589b68f 100644 --- a/apps/web/src/connectors/webauthn.html +++ b/apps/web/src/connectors/webauthn.html @@ -9,7 +9,7 @@ diff --git a/apps/web/src/images/integrations/logo-sumo-logic-siem-darkmode.svg b/apps/web/src/images/integrations/logo-sumo-logic-siem-darkmode.svg new file mode 100644 index 00000000000..cbd9e1555f0 --- /dev/null +++ b/apps/web/src/images/integrations/logo-sumo-logic-siem-darkmode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/integrations/logo-sumo-logic-siem.svg b/apps/web/src/images/integrations/logo-sumo-logic-siem.svg new file mode 100644 index 00000000000..1d584be72dd --- /dev/null +++ b/apps/web/src/images/integrations/logo-sumo-logic-siem.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/src/images/loading-white.svg b/apps/web/src/images/loading-white.svg index ef5970da42e..f5546e56340 100644 --- a/apps/web/src/images/loading-white.svg +++ b/apps/web/src/images/loading-white.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/images/loading.svg b/apps/web/src/images/loading.svg index 5f4102a5921..e05a42f6c70 100644 --- a/apps/web/src/images/loading.svg +++ b/apps/web/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 0b1ea74fc6c..f63a0878540 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Gunstelinge" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipes" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Werk Blaaier By" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Gewysigde beleid $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "U hoofwagwoord voldoen nie aan een of meer van die organisasiebeleide nie. Om toegang tot die kluis te kry, moet u nou u hoofwagwoord bywerk. Deur voort te gaan sal u van u huidige sessie afgeteken word, en u sal weer moet aanteken. Aktiewe sessies op ander toestelle kan vir tot een uur aktief bly." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Toegang geweiger. U het nie toestemming om hierdie blad te sien nie." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Hoofwagwoord" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index fa84a235bff..30eddbb1702 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "الوصول إلى الذكاء" }, - "riskInsights": { - "message": "رؤى المخاطر" - }, "passwordRisk": { "message": "مخاطر كلمة المرور" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "راجع كلمات المرور المعرضة للخطر (الضعيفة، المكشوفة، أو المعاد استخدامها) عبر التطبيقات. اختر تطبيقاتك الحرجة لتحديد أولويات إجراءات الأمان لمستخدميك لمعالجة كلمات المرور المعرضة للخطر." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "آخر تحديث للبيانات: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "التطبيقات المعلَّمة كتطبيقات حرجة" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "المفضلة" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "الفئات" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "كلمة المرور الرئيسية" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 64a5dc373f6..a09fa41ed90 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Məlumatları" - }, "passwordRisk": { "message": "Parol riski" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Tətbiqlər arasında riskli (zəif, ifşa olunmuş və ya təkrar istifadə olunmuş) parolları incələyin. İstifadəçilərinizin riskli parollara yönəlmiş təhlükəsizlik tədbirlərinə əhəmiyyət vermələri üçün kritik tətbiqlərinizi seçin." }, + "reviewAtRiskLoginsPrompt": { + "message": "Riskli girişləri incələ" + }, "dataLastUpdated": { "message": "Verilərin son güncəlləmə tarixi: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "kritik olaraq işarələnmiş tətbiqlər" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritik tətbiq", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "$ORG NAME$ üçün heç bir tətbiq tapılmadı", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Kimlik məlumatı təhlükəsizlik risklərini monitorinq etməyə başlamaq üçün təşkilatınızın giriş verilərini daxilə köçürün. Daxilə köçürdükdən sonra əldə edəcəkləriniz:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Riskləri prioritetləşdirmə" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Ən vacib tətbiqlərə fokuslanma" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Riskləri azaltma bələdçisi" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Risk altındakı üzvlərə, riskli kimlik məlumatlarını dəyişmək üçün rəhbər tapşırıqlar təyin edin" }, "benefit3Title": { - "message": "Monitor progress" + "message": "İrəliləyişin monitorinqi" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Təhlükəsizlik təkmilləşdirmələrini göstərmək üçün zamanla dəyişiklikləri izləyin" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Tətbiqləri görmək üçün ilk hesabatınızı çalışdırın" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Təşkilatınızın tətbiqlərini təhlil etmək və diqqət edilməli riskli parolları müəyyənləşdirmək üçün risk təhlili hesabatını yaradın. İlk hesabatınızı çalışdırdıqda:" }, "noCriticalApplicationsTitle": { "message": "Heç bir tətbiqi kritik olaraq işarələməmisiniz" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Kritik olaraq işarələnmiş tətbiqlər" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ tətbiq kritik olaraq işarələndi", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Müraciətlər kritik olaraq işarələnmədi" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Kritik tətbiqlər üçün risk altındakı elementlərə erişimi olan üzvlər" }, + "membersWithAtRiskPasswords": { + "message": "Riskli parollara sahib üzvlər" + }, + "membersWillReceiveNotification": { + "message": "Üzvlər, riskli girişləri həll etmək üçün brauzer uzantısı üzərindən bildiriş alacaqlar." + }, "membersAtRiskCount": { "message": "$COUNT$ üzv risk altındadır", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "İndi incələ" }, + "allCaughtUp": { + "message": "Hələlik bu qədər!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Hazırda incələnməli yeni tətbiq yoxdur" + }, "prioritizeCriticalApplications": { "message": "Kritik tətbiqləri prioritetləşdir" }, - "atRiskItems": { - "message": "Riskli elementlər" + "selectCriticalApplicationsDescription": { + "message": "Təşkilatınız üçün ən kritik sayılan tətbiqləri seçin, daha sonra riskləri həll etmələri üçün üzvlərə təhlükəsizlik tapşırıqları təyin edin." + }, + "reviewNewApplications": { + "message": "Yeni tətbiqlər incələ" + }, + "reviewNewApplicationsDescription": { + "message": "Admin konsolunda saxlanılmış və zəif, ifşa olunmuş və ya təkrar istifadə olunmuş parollara sahib yeni tətbiqlər üçün riskli elementləri vurğulamışıq." + }, + "clickIconToMarkAppAsCritical": { + "message": "Bir tətbiqi kritik olaraq işarələmək üçün ulduz ikonuna klikləyin" }, "markAsCriticalPlaceholder": { "message": "Kritik olaraq işarələmə funksionallığı, gələcək güncəlləmədə tətbiq olunacaq" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Tətbiq incələmə saxlanıldı" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Yeni tətbiqlər incələnib" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "İncələmə statusunu saxlama xətası" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Lütfən yenidən sınayın" }, "unmarkAsCritical": { "message": "Kritik olaraq işarələməni götür" @@ -835,6 +862,9 @@ "favorites": { "message": "Sevimlilər" }, + "taskSummary": { + "message": "Tapşırıq icmalı" + }, "types": { "message": "Növlər" }, @@ -1360,7 +1390,7 @@ "message": "Vahid daxil olma üsulunu istifadə et" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Təşkilatınız, vahid daxil olma tələb edir." }, "welcomeBack": { "message": "Yenidən xoş gəlmisiniz" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Brauzeri güncəllə" }, - "generatingYourRiskInsights": { - "message": "Risk Təhlilləriniz yaradılır..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Hesabatı işə sal" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Vahid təşkilat siyasəti tələb olunur. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Birdən çox təşkilatın üzvü olan hər kəsin erişimi, digər təşkilatları tərk edənə qədər ləğv ediləcək." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Bu avtomatlaşdırmanı aktivləşdirmək üçün bütün üzvlər yalnız bu təşkilata aid olmalıdır." }, "autoConfirmSingleOrgExemption": { "message": "Vahid təşkilat siyasəti bütün rollara şamil ediləcək. " @@ -5872,6 +5902,19 @@ "message": "\"Send\" yaradarkən və ya ona düzəliş edərkən istifadəçilərin e-poçt ünvanlarını alıcılardan gizlətməsinə icazə verməyin.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "İlkin URI uyuşma aşkarlaması" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Avto-doldurma üçün giriş məlumatlarının nə vaxt təklif ediləcəyini müəyyənləşdirin. Adminlər və sahiblər bu siyasətdən azaddır." + }, + "uriMatchDetectionOptionsLabel": { + "message": "İlkin URI uyuşma aşkarlaması" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Lütfən yararlı bir URI uyuşma aşkarlama variantını seçin.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ siyasətinə düzəliş edildi.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə erişmək üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." }, - "automaticAppLogin": { - "message": "İcazə verilən tətbiqlər üçün istifadəçilərin avtomatik giriş etməsi" + "automaticAppLoginWithSSO": { + "message": "SSO ilə avtomatik giriş" }, - "automaticAppLoginDesc": { - "message": "Konfiqurasiya edilmiş kimlik provayderinizdən başladılan tətbiqlər üçün giriş xanaları avtomatik doldurulub göndəriləcək." + "automaticAppLoginWithSSODesc": { + "message": "SSO təhlükəsizliyini və rahatlığını idarə edilməyən tətbiqlərə genişləndirin. İstifadəçilər bir tətbiqi kimlik provayderinizdən işə saldığı zaman, onların giriş məlumatları avtomatik olaraq doldurulub təqdim ediləcək. Beləliklə kimlik provayderindən tətbiqə bir kliklə təhlükəsiz axın yaradılır." }, "automaticAppLoginIdpHostLabel": { "message": "Provayder host-unu müəyyənləşdir" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-lər, HTTPS istifadə etməlidir." + }, "apiUrl": { "message": "API server URL-si" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Erişim rədd edildi. Bu səhifəyə baxmaq üçün icazəniz yoxdur." }, + "noPageAccess": { + "message": "Bu səhifəyə erişmək üçün icazəniz yoxdur" + }, "masterPassword": { "message": "Ana parol" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Tapşırıq təyin et" }, + "assignTasksToMembers": { + "message": "Üzvlərə rəhbər həll üçün tapşırıqlar təyin edin" + }, "assignToCollections": { "message": "Kolleksiyalara təyin et" }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 21713eb5af8..21639cddbcc 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Кіраванне доступам" }, - "riskInsights": { - "message": "Разуменне рызык" - }, "passwordRisk": { "message": "Рызыка пароля" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Праглядайце паролі, якія знаходзяцца ў зоне рызыкі (ненадзейныя, скампраметаваныя або паўторна выкарыстаныя) ва ўсіх вашых праграмах. Выберыце найбольш крытычныя праграмы для вызначэння прыярытэту бяспекі дзеянняў для вашых карыстальнікаў, якія выкарыстоўваюць рызыкоўныя паролі." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Апошняе абнаўленне даных: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Абраныя" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Тыпы" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Абнавіць браўзер" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Заўсёды паказваць атрымальнікам адрас электроннай пошты ўдзельніка пры стварэнні або рэдагаванні Send'a.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Змяненне палітыкі $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш асноўны пароль не адпавядае адной або некалькім палітыкам арганізацыі. Для атрымання доступу да сховішча, вы павінны абнавіць яго. Працягваючы, вы выйдзіце з бягучага сеанса і вам неабходна будзе ўвайсці паўторна. Актыўныя сеансы на іншых прыладах могуць заставацца актыўнымі на працягу адной гадзіны." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Доступ забаронены. У вас не дастаткова правоў для прагляду гэтай старонкі." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Асноўны пароль" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index f093f825ac2..cc6e298c26e 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Подробности за рисковете" - }, "passwordRisk": { "message": "Рискова парола" }, + "noEditPermissions": { + "message": "Нямате право за редактиране на този елемент" + }, "reviewAtRiskPasswords": { "message": "Прегледайте паролите в риск (слаби, преизползвани или разобличени) в различните приложения. Изберете най-важните си приложения, за да дадете приоритет на действията по сигурността за потребителите си, така че те да обърнат внимание на паролите си в риск." }, + "reviewAtRiskLoginsPrompt": { + "message": "Преглед на елементите за вписване в риск" + }, "dataLastUpdated": { "message": "Последно обновяване на данните: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "важни приложения са отбелязани" + }, "countOfCriticalApplications": { "message": "$COUNT$ важни приложения", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Няма намерени приложения за $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Внесете данните за вписване на организацията си, за да започнете да следите рисковете по сигурността им. След внасянето ще можете:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Да приоритизирате рисковете" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Фокусирайте се върху приложенията, които са най-важни" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Да давате насоки за подобряване" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Давайте задачи на членовете в риск, така че те да не забравят да променят данните си" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Да следите напредъка" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Следете промените във времето, за да виждате как сигурността се подобрява" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Създайте първия си доклад, за да видите приложенията" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Създайте доклад с подробности за рисковете, за да анализирате приложенията в организацията си и да установите кои пароли са в риск и имат нужда от внимание. Създаването на първия доклад ще:" }, "noCriticalApplicationsTitle": { "message": "Не сте отбелязали нито едно приложение като важно" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ приложения са отбелязани като важни", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Приложенията не успяха да бъдат отбелязани като важни" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Членове с достъп до елементи в риск за важни приложения" }, + "membersWithAtRiskPasswords": { + "message": "Членове с пароли в риск" + }, + "membersWillReceiveNotification": { + "message": "Членовете ще получат известие, за да отстранят проблема с данните си за вписване в риск, чрез добавката за браузъра." + }, "membersAtRiskCount": { "message": "$COUNT$ членове в риск", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Преглеждане сега" }, + "allCaughtUp": { + "message": "Всичко е изпълнено!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "В момента няма нови приложения за преглед" + }, "prioritizeCriticalApplications": { "message": "Даване на приоритет на важните приложения" }, - "atRiskItems": { - "message": "Елементи в риск" + "selectCriticalApplicationsDescription": { + "message": "Изберете кои приложения са най-важни за организацията Ви, а след това раздайте задачи на членовете, за да отстраните рисковете." + }, + "reviewNewApplications": { + "message": "Преглед на новите приложения" + }, + "reviewNewApplicationsDescription": { + "message": "Отбелязахме елементите в риск за новите приложения съхранявани в Административната конзола, които имат слаби, преизползвани или разкрити пароли." + }, + "clickIconToMarkAppAsCritical": { + "message": "Щракнете върху иконката със звезда, за да отбележите приложение като важно" }, "markAsCriticalPlaceholder": { "message": "Функционалността за отбелязване на нещо като важно ще бъде добавена в бъдеща версия" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Прегледът на приложението е запазен" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Има нови прегледани приложения" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Грешка при запазването на състоянието на преглед" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Моля, опитайте отново" }, "unmarkAsCritical": { "message": "Премахване от важните" @@ -835,6 +862,9 @@ "favorites": { "message": "Любими" }, + "taskSummary": { + "message": "Обобщение на задачата" + }, "types": { "message": "Видове" }, @@ -1360,7 +1390,7 @@ "message": "Използване на еднократна идентификация" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Вашата организация изисква еднократно удостоверяване." }, "welcomeBack": { "message": "Добре дошли отново" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Обновяване на браузъра" }, - "generatingYourRiskInsights": { - "message": "Създаване на Вашата информация относно рисковете…" + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Изпълнение на доклада" @@ -5752,7 +5782,7 @@ "message": "няма да бъде автоматично избрана при създаване на нови елементи" }, "organizationDataOwnershipWarning3": { - "message": "не може да се управлява от Конзолата за администриране, докато потребителят не бъде премахнат" + "message": "не може да се управлява от Административната конзола, докато потребителят не бъде премахнат" }, "organizationDataOwnershipWarningContentTop": { "message": "Изключвайки тази политика, стандартната колекция: " @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Изисква се да е включена политиката за единствена организация. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Достъпът ще бъде преустановен за всеки, който е част от повече от една организация, докато не напусне другите организации." + "autoConfirmSingleOrgRequiredDesc": { + "message": "За да може да се включи тази автоматизация, всички членове трябва да принадлежат само на тази организация." }, "autoConfirmSingleOrgExemption": { "message": "Политиката за единствена организация ще се прилага за всички роли. " @@ -5872,6 +5902,19 @@ "message": "Потребителите да не могат да скриват адреса на е-пощата си от получателите, когато създават или редактират изпращания.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Стандартно засичане на съвпадения на адреси" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Определя кога данните за вписване да се предлагат за автоматично попълване. Тази политика не засяга администраторите и собствениците." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Стандартно засичане на съвпадения на адреси" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Моля, изберете вариант за засичане на съвпаденията на адреси.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Редактирана политика № $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Вашата главна парола не отговаря на една или повече политики на организацията Ви. За да получите достъп до трезора, трябва да промените главната си парола сега. Това означава, че ще бъдете отписан(а) от текущата си сесия и ще трябва да се впишете отново. Активните сесии на други устройства може да продължат да бъдат активни още един час." }, - "automaticAppLogin": { - "message": "Автоматично вписване на потребителите за разрешените приложения" + "automaticAppLoginWithSSO": { + "message": "Автоматично вписване чрез еднократно удостоверяване" }, - "automaticAppLoginDesc": { - "message": "Формулярите за вписване ще бъдат попълвани и изпращани автоматично за приложенията, стартирани от настроения Ви доставчик на самоличност." + "automaticAppLoginWithSSODesc": { + "message": "Възползвайте се от сигурността и удобството на еднократното удостоверяване дори и за неуправлявани приложения. Когато потребителите пуснат приложение от Вашия доставчик на самоличности, данните им за вписване ще бъдат автоматично попълнени и изпратени, с едно щракване и по безопасен път от доставчика на самоличност до приложението." }, "automaticAppLoginIdpHostLabel": { "message": "Сървър на доставчика на самоличност" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." }, + "selfHostedEnvMustUseHttps": { + "message": "Адресите трябва да ползват HTTPS." + }, "apiUrl": { "message": "Адрес на сървъра за ППИ" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Достъпът е отказан. Нямате право за преглед на тази страница." }, + "noPageAccess": { + "message": "Нямате достъп до тази страница" + }, "masterPassword": { "message": "Главна парола" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Назначаване на задачи" }, + "assignTasksToMembers": { + "message": "Задавайте задачи на членовете, за да отстраняват проблемите" + }, "assignToCollections": { "message": "Свързване с колекции" }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index acb63d2eb01..ec567fd43e3 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "প্রিয়গুলো" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "প্রকার" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index cd2cc6bd85a..c2150c22154 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoriti" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 8db6c9e6749..7b0094fdecd 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Intel·ligència d'accés" }, - "riskInsights": { - "message": "Coneixements de risc" - }, "passwordRisk": { "message": "Risc de contrasenya" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Reviseu les contrasenyes de risc (febles, exposades o reutilitzades) a totes les aplicacions. Seleccioneu les aplicacions més crítiques per prioritzar les accions de seguretat perquè els usuaris aborden les contrasenyes de risc." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Última actualització de les dades: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Preferits" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipus" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Actualitza el navegador" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Mostra sempre l'adreça de correu electrònic del membre amb els destinataris quan creeu o editeu un Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Política modificada $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "La vostra contrasenya mestra no compleix una o més de les polítiques de l'organització. Per accedir a la caixa forta, heu d'actualitzar-la ara. Si continueu, es tancarà la sessió actual i us demanarà que torneu a iniciar-la. Les sessions en altres dispositius poden continuar romanent actives fins a una hora." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL del servidor API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Accés denegat. No teniu permís per veure aquesta pàgina." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Contrasenya mestra" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assigna a col·leccions" }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index 433b820b7a0..bb5958b4e59 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Přístup k inteligenci" }, - "riskInsights": { - "message": "Poznatky o rizicích" - }, "passwordRisk": { "message": "Rizikové heslo" }, + "noEditPermissions": { + "message": "Nemáte oprávnění upravit tuto položku" + }, "reviewAtRiskPasswords": { "message": "Zkontrolujte ohrožená hesla (slabá, odhalená nebo opakovaně používaná) ve všech aplikacích. Vyberte nejkritičtější aplikace a stanovte priority bezpečnostních opatření pro uživatele, abyste se vypořádali s ohroženými hesly." }, + "reviewAtRiskLoginsPrompt": { + "message": "Zkontrolovat ohrožená přihlášení" + }, "dataLastUpdated": { "message": "Data naposledy aktualizována: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "označených kritických aplikací" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritických aplikací", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplikace označené jako kritické" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ aplikací označených jako kritických", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Nepodařilo se označit aplikace jako kritické" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Členové s přístupem k rizikovým položkám pro kritické aplikace" }, + "membersWithAtRiskPasswords": { + "message": "Členové s ohroženými hesly" + }, + "membersWillReceiveNotification": { + "message": "Členové obdrží oznámení, aby vyřešili ohrožená přihlášení prostřednictvím rozšíření prohlížeče." + }, "membersAtRiskCount": { "message": "$COUNT$ členů v ohrožení", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Zkontrolovat nyní" }, + "allCaughtUp": { + "message": "Vše je hotovo!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Nyní nejsou k dispozici žádné nové kontroly" + }, "prioritizeCriticalApplications": { "message": "Upřednostnit kritické aplikace" }, - "atRiskItems": { - "message": "Položky v ohrožení" + "selectCriticalApplicationsDescription": { + "message": "Vybere, které aplikace jsou pro Vaši organizaci nejkritičtější, a pro řešení rizik přiřaďte členům bezpečnostní úkoly." + }, + "reviewNewApplications": { + "message": "Zkontrolovat nové aplikace" + }, + "reviewNewApplicationsDescription": { + "message": "Zvýraznili jsme rizikové položky pro nové aplikace uložené v administrátorské konzoli, které mají slabá, exponovaná nebo znovu použitá hesla." + }, + "clickIconToMarkAppAsCritical": { + "message": "Klepnutím na ikonu hvězdičky označte aplikaci jako kritickou" }, "markAsCriticalPlaceholder": { "message": "Funkce označení jako kritické bude implementována v budoucí aktualizaci" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Kontrola aplikace uložena" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ aplikací označeno jako kritické", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "Nové aplikace zkontrolovány" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Oblíbené" }, + "taskSummary": { + "message": "Shrnutí úkolů" + }, "types": { "message": "Typy" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Aktualizovat prohlížeč" }, - "generatingYourRiskInsights": { - "message": "Generování poznatků o rizicích..." + "generatingYourAccessIntelligence": { + "message": "Generování Vaší přístupové inteligence..." }, "riskInsightsRunReport": { "message": "Spustit hlášení" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Vyžadují se jednotné zásady organizace. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Kdokoli, kdo je členem více než jedné organizace, bude mít přístup odvolán do té doby, než opustí ostatní organizace." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Pro aktivaci této automatizace musí všichni členové patřit pouze do této organizace." }, "autoConfirmSingleOrgExemption": { "message": "Jednotné zásady organizace se budou vztahovat na všechny role. " @@ -5872,6 +5902,19 @@ "message": "Při vytváření nebo úpravách Send vždy zobrazí e-mailovou adresu člena s příjemci.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Výchozí zjišťování shody URI" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Určete, kdy jsou přihlášení navržena pro automatické vyplňování. Administrátoři a vlastníci jsou od těchto zásad osvobozeni." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Výchozí zjišťování shody URI" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Vyberte platnou volbu detekce shody URI.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Byly změněny zásady $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Vaše hlavní heslo nesplňuje jednu nebo více zásad Vaší organizace. Pro přístup k trezoru musíte nyní aktualizovat své hlavní heslo. Pokračování Vás odhlásí z Vaší aktuální relace a bude nutné se přihlásit. Aktivní relace na jiných zařízeních mohou zůstat aktivní až po dobu jedné hodiny." }, - "automaticAppLogin": { - "message": "Automaticky přihlásit uživatele pro povolené aplikace" + "automaticAppLoginWithSSO": { + "message": "Automatické přihlášení pomocí SSO" }, - "automaticAppLoginDesc": { - "message": "Přihlašovací formuláře budou automaticky vyplněny a odeslány pro aplikace spuštěné od Vašeho nakonfigurovaného poskytovatele identity." + "automaticAppLoginWithSSODesc": { + "message": "Rozšiřte zabezpečení a pohodlí SSO i na nespravované aplikace. Když uživatelé spustí aplikaci od Vašeho poskytovatele identit, jejich přihlašovací údaje se automaticky vyplní a odešlou, čímž se vytvoří bezpečný tok od poskytovatele identit k aplikaci jedním klepnutím." }, "automaticAppLoginIdpHostLabel": { "message": "Hostitel poskytovatele identity" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." }, + "selfHostedEnvMustUseHttps": { + "message": "URL adresy musí používat HTTPS." + }, "apiUrl": { "message": "URL API serveru" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Přístup byl odepřen. Nemáte oprávnění k zobrazení této stránky." }, + "noPageAccess": { + "message": "Nemáte přístup k této stránce" + }, "masterPassword": { "message": "Hlavní heslo" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Přiřadit úkoly" }, + "assignTasksToMembers": { + "message": "Přiřadit úkoly členům k řízenému řešení" + }, "assignToCollections": { "message": "Přiřadit ke sbírkám" }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 49f2f0ffcc3..cf5270e3d74 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index aff7bfc0168..543f846b078 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Adgangsefterretning" }, - "riskInsights": { - "message": "Risikoindsigt" - }, "passwordRisk": { "message": "Adgangskoderisiko" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Gennemgå risikobetonede adgangskoder (svage, eksponerede eller genbrugte) på tværs af applikationer. Vælg de mest kritiske applikationer for at prioritere sikkerhedshandlinger for brugerne til at håndtere risikobetonede adgangskoder." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data senest opdateret: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritter" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Typer" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Opdatér browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Vis altid medlemmets e-mailadresse med modtagere, når Sends oprettes eller redigeres.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Redigerede politik $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Din hovedadgangskode opfylder ikke et eller flere organisationspolitikkrav. For at kunne tilgå boksen skal hovedadgangskode derfor opdateres nu. Fortsættes, logges du ud af den nuværende session og vil skulle logge ind igen. Aktive sessioner på andre enheder kan forblive aktive i op til én time." }, - "automaticAppLogin": { - "message": "Log automatisk brugere på tilladte applikationer" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login-formularer udfyldes og indsendes automatisk for apps startet fra den opsatte identitetsudbyder." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identitetsudbydervært" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Der skal tilføjes enten basis Server-URL'en eller mindst ét tilpasset miljø." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API-server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Adgang nægtet. Nødvendig tilladelse til at se siden mangler." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Hovedadgangskode" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Tildel til samlinger" }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 8f72a5d2f01..57f2a8699f5 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Zugriff auf Informationen" }, - "riskInsights": { - "message": "Risiko-Überblick" - }, "passwordRisk": { "message": "Passwort-Risiko" }, + "noEditPermissions": { + "message": "Du bist nicht berechtigt, diesen Eintrag zu bearbeiten" + }, "reviewAtRiskPasswords": { "message": "Überprüfe gefährdete Passwörter (schwach, kompromittiert oder wiederverwendet) in allen Anwendungen. Wähle deine kritischsten Anwendungen aus, um die Sicherheitsmaßnahmen für deine Benutzer zu priorisieren, um gefährdete Passwörter zu beseitigen." }, + "reviewAtRiskLoginsPrompt": { + "message": "Überprüfung gefährdeter Zugangsdaten" + }, "dataLastUpdated": { "message": "Daten zuletzt aktualisiert: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "kritische Anwendungen markiert" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritische Anwendungen", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Keine Anwendungen für $ORG NAME$ gefunden", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Importiere die Zugangsdaten deiner Organisation, um mit der Überwachung von Sicherheitsrisiken für Zugangsdaten zu starten. Nach dem Import kannst du:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Risiken priorisieren" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Auf Anwendungen konzentrieren, die am wichtigsten sind" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Anleitung zur Behebung" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Gefährdeten Mitgliedern gezielte Aufgaben zuweisen, um gefährdete Zugangsdaten zu erneuern" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Fortschritt überwachen" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Änderungen im Laufe der Zeit verfolgen, um Sicherheitsverbesserungen anzuzeigen" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Erstelle deinen ersten Bericht, um Anwendungen zu sehen" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Erstelle einen Risikoanalysebericht, um die Anwendungen deines Unternehmens zu analysieren und gefährdete Passwörter zu identifizieren, die Aufmerksamkeit erfordern. Die Erstellung deines ersten Berichts wird:" }, "noCriticalApplicationsTitle": { "message": "Du hast keine Anwendung als kritisch markiert" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Als kritisch markierte Anwendungen" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ Anwendungen als kritisch markiert", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Anwendungen konnten nicht als kritisch markiert werden" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Mitglieder mit Zugriff auf gefährdete Einträge für kritische Anwendungen" }, + "membersWithAtRiskPasswords": { + "message": "Mitglieder mit gefährdeten Passwörtern" + }, + "membersWillReceiveNotification": { + "message": "Mitglieder erhalten über die Browser-Erweiterung eine Benachrichtigung, um gefährdete Zugangsdaten zu ändern." + }, "membersAtRiskCount": { "message": "$COUNT$ gefährdete Mitglieder", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Jetzt prüfen" }, + "allCaughtUp": { + "message": "Alles erledigt!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Derzeit gibt es keine neuen Anwendungen zum Überprüfen" + }, "prioritizeCriticalApplications": { "message": "Kritische Anwendungen priorisieren" }, - "atRiskItems": { - "message": "Gefährdete Einträge" + "selectCriticalApplicationsDescription": { + "message": "Wähle die für deine Organisation kritischsten Anwendungen aus und weise den Mitgliedern Sicherheitsaufgaben zu, um Risiken zu beseitigen." + }, + "reviewNewApplications": { + "message": "Neue Anwendungen überprüfen" + }, + "reviewNewApplicationsDescription": { + "message": "Wir haben gefährdete Einträge für neue Anwendungen hervorgehoben, die in der Administrator-Konsole gespeichert sind und schwache, kompromittierte oder wiederverwendete Passwörter beinhalten." + }, + "clickIconToMarkAppAsCritical": { + "message": "Klicke auf das Sternsymbol, um eine App als kritisch zu markieren" }, "markAsCriticalPlaceholder": { "message": "Die Funktion \"Als kritisch markieren\" wird in einer zukünftigen Aktualisierung implementiert" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Anwendungsüberprüfung gespeichert" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Neue Anwendungen überprüft" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Fehler beim Speichern des Überprüfungsstatus" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Bitte versuche es erneut" }, "unmarkAsCritical": { "message": "Markierung als kritisch aufheben" @@ -835,6 +862,9 @@ "favorites": { "message": "Favoriten" }, + "taskSummary": { + "message": "Aufgaben-Übersicht" + }, "types": { "message": "Typen" }, @@ -1360,7 +1390,7 @@ "message": "Single Sign-on verwenden" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Deine Organisation erfordert Single Sign-On." }, "welcomeBack": { "message": "Willkommen zurück" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Browser aktualisieren" }, - "generatingYourRiskInsights": { - "message": "Dein Risiko-Überblick wird generiert..." + "generatingYourAccessIntelligence": { + "message": "Deine Zugangsintelligenz wird generiert..." }, "riskInsightsRunReport": { "message": "Bericht ausführen" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Richtlinie einer einzelnen Organisation erforderlich. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Jeder, der mehr als einer Organisation angehört, verliert seinen Zugriff, bis er aus den anderen Organisationen austritt." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Alle Mitglieder dürfen nur dieser Organisation angehören, um diese Automatisierung zu aktivieren." }, "autoConfirmSingleOrgExemption": { "message": "Die Richtlinie für einzelne Organisationen wird für alle Rollen gelten. " @@ -5872,6 +5902,19 @@ "message": "Benutzern nicht gestatten, ihre E-Mail-Adresse vor Empfängern zu verstecken, wenn sie ein Send erstellen oder bearbeiten.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Standard URI-Übereinstimmungserkennung" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Lege fest, wann Zugangsdaten für Auto-Ausfüllen vorgeschlagen werden. Administratoren und Eigentümer sind von dieser Richtlinie ausgenommen." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Standard URI-Übereinstimmungserkennung" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Bitte wähle eine gültige URI-Übereinstimmungserkennungs-Option aus.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Richtlinie $ID$ geändert.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Dein Master-Passwort entspricht nicht einer oder mehreren Richtlinien deiner Organisation. Um auf den Tresor zugreifen zu können, musst du dein Master-Passwort jetzt aktualisieren. Wenn du fortfährst, wirst du von deiner aktuellen Sitzung abgemeldet und musst dich erneut anmelden. Aktive Sitzungen auf anderen Geräten können noch bis zu einer Stunde lang aktiv bleiben." }, - "automaticAppLogin": { - "message": "Benutzer automatisch bei erlaubten Anwendungen anmelden" + "automaticAppLoginWithSSO": { + "message": "Automatische Anmeldung mit SSO" }, - "automaticAppLoginDesc": { - "message": "Anmeldeformulare von Apps, die über deinen konfigurierten Identitätsanbieter gestartet wurden, werden automatisch ausgefüllt und abgesendet." + "automaticAppLoginWithSSODesc": { + "message": "Erweitere die Sicherheit und den Komfort von SSO auf nicht verwaltete Anwendungen. Wenn Benutzer eine Anwendung über Ihren Identitätsanbieter starten, werden deren Anmeldedaten automatisch ausgefüllt und übermittelt, sodass ein sicherer Ablauf mit nur einem Klick vom Identitätsanbieter zur Anwendung entsteht." }, "automaticAppLoginIdpHostLabel": { "message": "Identitätsanbieter Host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs müssen HTTPS verwenden." + }, "apiUrl": { "message": "API Server-URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Zugriff verweigert. Du hast keine Berechtigung, diese Seite zu sehen." }, + "noPageAccess": { + "message": "Du hast keinen Zugriff auf diese Seite" + }, "masterPassword": { "message": "Master-Passwort" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Aufgaben zuweisen" }, + "assignTasksToMembers": { + "message": "Mitgliedern Aufgaben für eine gezielte Lösung zuweisen" + }, "assignToCollections": { "message": "Sammlungen zuweisen" }, @@ -12022,6 +12074,6 @@ "message": "Kartennummer" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Kostenlose Families-Testversion starten" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 0e1dd5e6399..641c789f363 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Πληροφορίες Πρόσβασης" }, - "riskInsights": { - "message": "Insights Κινδύνου" - }, "passwordRisk": { "message": "Ρίσκος Κωδικού Πρόσβασης" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Ελέξτε τους κωδικούς πρόσβασης (αδύναμους, εκτεθειμένους ή επαναχρησιμοποιούμενους) σε όλες τις εφαρμογές. Επιλέξτε τις πιο κρίσιμες εφαρμογές σας για να δώσετε προτεραιότητα στις ενέργειες ασφαλείας για τους χρήστες σας ώστε να αντιμετωπίσουν τους εκτεθειμένους κωδικούς πρόσβασης." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Τελευταία ενημέρωση δεδομένων: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Αγαπημένα" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Τύποι" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Ενημερώστε τον Browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Μην επιτρέπετε στους χρήστες να αποκρύψουν τη διεύθυνση email τους από τους παραλήπτες κατά τη δημιουργία ή την επεξεργασία ενός send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Τροποποιημένη πολιτική $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ο κύριος κωδικός πρόσβασής σας δεν πληροί μία ή περισσότερες πολιτικές του οργανισμού σας. Για να αποκτήσετε πρόσβαση στην κρύπτη, πρέπει να ενημερώσετε τον κύριο κωδικό πρόσβασής σας τώρα. Η διαδικασία αυτή θα σας αποσυνδέσει από την τρέχουσα συνεδρία σας, απαιτώντας από εσάς να συνδεθείτε ξανά. Οι ενεργές συνεδρίες σε άλλες συσκευές ενδέχεται να παραμείνουν ενεργές για μία ώρα." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Οι φόρμες σύνδεσης θα συμπληρώνονται αυτόματα και θα υποβάλλονται για τις εφαρμογές που εκκινούνται από τον καθορισμένο πάροχο ταυτότητάς σας." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL διακομιστή API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Κύριος κωδικός πρόσβασης" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Ανάθεση σε συλλογές" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 36d68429ac6..186bf29c441 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -328,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -352,8 +376,17 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -361,15 +394,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -841,6 +865,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4418,8 +4445,33 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." + }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." }, "riskInsightsRunReport": { "message": "Run report" @@ -5783,16 +5835,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -5814,8 +5866,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5881,6 +5933,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6534,11 +6599,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7313,6 +7378,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9488,9 +9556,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -9659,7 +9724,7 @@ "message": "Common formats", "description": "Label indicating the most common import formats" }, - "uriMatchDefaultStrategyHint": { + "uriMatchDefaultStrategyHint": { "message": "URI match detection is how Bitwarden identifies autofill suggestions.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, @@ -9771,6 +9836,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index cfda4b26853..01b96d1d207 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritise security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organisation, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favourites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organisation policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organisation will have their access revoked until they leave the other organisations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organisation to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organisation policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index ee4b318f47f..c1da6986bba 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritise security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organisation, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favourites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organisation policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organisation will have their access revoked until they leave the other organisations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organisation to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organisation policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Do not allow users to hide their email address from recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organisation policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 5a97cab4b86..909ac5df6f6 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoratoj" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipoj" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Ĝisdatigi retumilon" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Ĉefa pasvorto" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index dd95446fd0c..adb547b4982 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Inteligencia de Acceso" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Riesgo de contraseña" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritos" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipos" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Actualizar navegador" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "No permitir a los usuarios ocultar su dirección de correo electrónico a los destinatarios al crear o editar un Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Política modificada $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Su contraseña maestra no cumple con una o más de las políticas de su organización. Para acceder a la caja fuerte, debe actualizar su contraseña maestra ahora. Proceder le desconectará de su sesión actual, requiriendo que vuelva a iniciar sesión. Las sesiones activas en otros dispositivos pueden seguir estando activas durante hasta una hora." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes añadir o bien la URL del servidor base, o al menos un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL del servidor de la API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Acceso denegado. No tiene permiso para ver esta página." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Contraseña maestra" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Asignar a colecciones" }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 9142554d701..c26994bcf21 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Viimati uuendatud: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Lemmikud" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tüübid" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Uuenda brauserit" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Ära luba kasutajatel Sendi loomisel või muutmisel oma e-posti aadressi saajate eest peita.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Muutis poliitikat $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Logi kasutajad lubatud rakendustesse automaatselt sisse" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Ligipääs keelatud. Sul pole lubatud seda lehekülge vaadata." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Ülemparool" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index dbfcd903a5d..58282adf95f 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Gogokoak" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Motak" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Nabigatzailea eguneratu" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Send bat sortzean edo editatzean, erakutsi beti kidearen emaila hartzaileari.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ politika aldatua.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Sarbidea ukatuta. Ez duzu baimenik orri hau ikusteko." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Pasahitz nagusia" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 1c0fbbde367..cf497f9ac86 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "دسترسی به هوش مصنوعی" }, - "riskInsights": { - "message": "دیدگاه‌های خطر" - }, "passwordRisk": { "message": "خطر کلمه عبور" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "کلمات عبور در معرض خطر (ضعیف، افشا شده یا تکراری) را در برنامه‌ها بررسی کنید. برنامه‌های حیاتی خود را انتخاب کنید تا اقدامات امنیتی را برای کاربران‌تان اولویت‌بندی کنید و به کلمات عبور در معرض خطر رسیدگی کنید." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "آخرین به‌روزرسانی داده‌ها: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "برنامه‌های علامت گذاری شده به عنوان حیاتی" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "مورد علاقه‌ها" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "انواع" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "به‌روزرسانی مرورگر" }, - "generatingYourRiskInsights": { - "message": "در حال تولید تحلیل‌های ریسک شما..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "هنگام ایجاد یا ویرایش ارسال، همیشه نشانی ایمیل اعضا را به گیرندگان نشان بده.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "سیاست تغییر یافته $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "کلمه عبور اصلی شما با یک یا چند سیاست سازمان‌تان مطابقت ندارد. برای دسترسی به گاوصندوق، باید همین حالا کلمه عبور اصلی خود را به‌روز کنید. در صورت ادامه، شما از نشست فعلی خود خارج می‌شوید و باید دوباره وارد سیستم شوید. نشست فعال در دستگاه های دیگر ممکن است تا یک ساعت همچنان فعال باقی بمانند." }, - "automaticAppLogin": { - "message": "ورود خودکار کاربران به برنامه‌های مجاز" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "فرم‌های ورود به‌صورت خودکار پر شده و برای برنامه‌هایی که از ارائه‌دهنده هویت پیکربندی شده شما اجرا می‌شوند، ارسال خواهند شد." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "میزبان ارائه دهنده هویت" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "نشانی API سرور" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "دسترسی رد شد. شما اجازه مشاهده این صفحه را ندارید." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "کلمه عبور اصلی" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "اختصاص به مجموعه‌ها" }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index d39b002e185..383572bb5d7 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Salasanariski" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Tiedot päivitetty viimeksi: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Suosikit" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tyypit" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Päivitä selain" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Näytä jäsenen sähköpostiosoite aina vastaanottajien ohessa, kun Send luodaan tai sitä muokataan.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Muokkasi käytäntöä \"$ID$\".", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Pääsalasanasi ei täytä yhden tai useamman organisaatiokäytännön vaatimuksia ja holvin käyttämiseksi sinun on vaihdettava se nyt. Tämä uloskirjaa kaikki nykyiset istunnot pakottaen uudelleenkirjautumisen. Muiden laitteiden aktiiviset istunnot saattavat toimia vielä tunnin ajan." }, - "automaticAppLogin": { - "message": "Kirjaa käyttäjät automaattisesti sisään sallittuihin sovelluksiin" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Kirjautumislomakkeet täytetään ja lähetetään automaattisesti määritetyltä identiteettitarjoaltasi käynnistetyissä sovelluksissa." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identiteettitarjoajan osoite" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Sinun on lisättävä joko palvelimen perusosoite tai ainakin yksi mukautettu palvelinympäristö." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API-palvelimen URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Pääsy estetty. Sinulla ei ole oikeutta avata sivua." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Pääsalasana" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Määritä kokoelmiin" }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index c38d950bc1d..e912c206e60 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Mga Paborito" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Mga uri" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update sa browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Laging ipakita ang email address ng miyembro sa mga tatanggap kapag lumilikha o nag edit ng isang Ipadala.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Binagong patakaran $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Hindi natanggap ang access. Wala kang pahintulot na tingnan ang pahinang ito." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master Password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index dd8a8c21ba1..14c04bde667 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Accéder à Intelligence" }, - "riskInsights": { - "message": "Aperçus des Risques" - }, "passwordRisk": { "message": "Risque du mot de passe" }, + "noEditPermissions": { + "message": "Vous n'avez pas l'autorisation de modifier cet élément" + }, "reviewAtRiskPasswords": { "message": "Examinez les mots de passe à risque (faibles, exposés ou réutilisés) à travers les applications. Sélectionnez vos applications les plus critiques pour prioriser les actions de sécurité pour que vos utilisateurs s'occupent des mots de passe à risque." }, + "reviewAtRiskLoginsPrompt": { + "message": "Examiner les identifiants à risque" + }, "dataLastUpdated": { "message": "Dernière mise à jour des données : $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "applications critiques marquées" + }, "countOfCriticalApplications": { "message": "$COUNT$ applications critiques", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marquées comme critiques" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marquées comme critiques", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Échec du marquage de l'application comme étant critique" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Les membres ayant accès aux éléments à risque pour les applications critiques" }, + "membersWithAtRiskPasswords": { + "message": "Membres avec des mots de passe à risque" + }, + "membersWillReceiveNotification": { + "message": "Les membres recevront une notification pour résoudre les identifiants à risque via l'extension du navigateur." + }, "membersAtRiskCount": { "message": "$COUNT$ membres à risque", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Examiner maintenant" }, + "allCaughtUp": { + "message": "Tout est à jour !" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Aucune nouvelle application à examiner pour le moment" + }, "prioritizeCriticalApplications": { "message": "Prioriser les applications critiques" }, - "atRiskItems": { - "message": "Éléments à risque" + "selectCriticalApplicationsDescription": { + "message": "Sélectionnez les applications les plus critiques pour votre organisation, puis attribuez des tâches de sécurité aux membres pour résoudre les risques." + }, + "reviewNewApplications": { + "message": "Examiner les nouvelles applications" + }, + "reviewNewApplicationsDescription": { + "message": "Nous avons mis en évidence des éléments à risque pour les nouvelles applications stockées dans la console Admin qui ont des mots de passe faibles, exposés ou réutilisés." + }, + "clickIconToMarkAppAsCritical": { + "message": "Cliquez sur l'icône étoile pour marquer une application comme critique" }, "markAsCriticalPlaceholder": { "message": "Marquer comme fonctionnalité critique sera implémentée dans une mise à jour future" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Révision de l'application enregistrée" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marquées comme critiques", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "Nouvelles demandes examinées" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoris" }, + "taskSummary": { + "message": "Résumé de tâche" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Mettre à jour le navigateur" }, - "generatingYourRiskInsights": { - "message": "Génération de vos Aperçus de Risque..." + "generatingYourAccessIntelligence": { + "message": "Génération de votre Intelligence d'Accès..." }, "riskInsightsRunReport": { "message": "Exécuter le rapport" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Une politique d'organisation unique est requise. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Toute personne faisant partie de plus d'une organisation sera révoquée jusqu'à ce qu'elle quitte les autres organisations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Tous les membres doivent appartenir uniquement à cette organisation pour activer cette automatisation." }, "autoConfirmSingleOrgExemption": { "message": "La politique d'organisation unique s'étendra à tous les rôles. " @@ -5872,6 +5902,19 @@ "message": "Toujours afficher l'adresse électronique du membre avec les destinataires lors de la création ou de l'édition d'un Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Détection de correspondance URL par défaut" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Détermine quand des identifiants sont suggérées pour la saisie automatique. Les administrateurs et les propriétaires sont exemptés de cette politique de sécurité." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Détection de correspondance URL par défaut" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Veuillez sélectionner une option de détection de correspondance URL valide.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Politique $ID$ modifiée.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Votre mot de passe principal ne répond pas aux exigences des politiques de sécurité de cette organisation. Pour pouvoir accéder au coffre, vous devez mettre à jour votre mot de passe principal dès maintenant. En poursuivant, vous serez déconnecté de votre session actuelle et vous devrez vous reconnecter. Les sessions actives sur d'autres appareils peuver rester actives pendant encore une heure." }, - "automaticAppLogin": { - "message": "Se connecter automatiquement aux utilisateurs pour les applications autorisées" + "automaticAppLoginWithSSO": { + "message": "Connexion automatique avec SSO" }, - "automaticAppLoginDesc": { - "message": "Les formulaires de connexion seront automatiquement remplis et soumis pour les applications lancées à partir de votre fournisseur d'identité configuré." + "automaticAppLoginWithSSODesc": { + "message": "Étendre la sécurité et la commodité SSO aux applications non gérées. Lorsque les utilisateurs lancent une application à partir de votre fournisseur d'identité, leurs identifiants de connexion sont automatiquement saisis et soumis, créant une connexion sécuriséw en un clic depuis le fournisseur d'identité vers l'application." }, "automaticAppLoginIdpHostLabel": { "message": "Hôte du fournisseur d'identité" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Vous devez ajouter soit l'URL du Serveur de base, soit au moins un environnement personnalisé." }, + "selfHostedEnvMustUseHttps": { + "message": "Les URLs doivent utiliser HTTPS." + }, "apiUrl": { "message": "URL du serveur API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Accès Refusé. Vous n'avez pas l'autorisation d'afficher cette page." }, + "noPageAccess": { + "message": "Vous n'avez pas accès à cette page" + }, "masterPassword": { "message": "Mot de passe principal" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assigner des tâches" }, + "assignTasksToMembers": { + "message": "Assigner des tâches aux membres pour une résolution guidée" + }, "assignToCollections": { "message": "Assigner aux collections" }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index fc561fdc46a..0a9c69acd9b 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritos" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipos" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index f28f566fd3d..473380c7e56 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -3,7 +3,7 @@ "message": "כל היישומים" }, "activity": { - "message": "Activity" + "message": "פעילות" }, "appLogoLabel": { "message": "הלוגו של Bitwarden" @@ -15,17 +15,20 @@ "message": "אין יישומים קריטיים בסיכון" }, "accessIntelligence": { - "message": "גישה למודיעין" - }, - "riskInsights": { - "message": "תובנות סיכון" + "message": "מודיעין גישות" }, "passwordRisk": { "message": "סיכון סיסמה" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "סקור סיסמאות בסיכון (חלשות, חשופות, או משומשות) בין יישומים. בחר את היישומים הכי קריטיים שלך על מנת לתעדף פעולות אבטחה עבור המשתמשים שלך כדי לטפל בסיסמאות בסיכון." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "הנתונים עודכנו לאחרונה: $DATE$", "placeholders": { @@ -36,7 +39,7 @@ } }, "noReportRan": { - "message": "You have not created a report yet" + "message": "עדיין לא יצרת דוח" }, "notifiedMembers": { "message": "חברים שהודיעו להם" @@ -63,7 +66,7 @@ "message": "צור פריט כניסה חדש" }, "percentageCompleted": { - "message": "$PERCENT$% complete", + "message": "$PERCENT$% הושלמו", "placeholders": { "percent": { "content": "$1", @@ -72,7 +75,7 @@ } }, "securityTasksCompleted": { - "message": "$COUNT$ out of $TOTAL$ security tasks completed", + "message": "$COUNT$ מתוך $TOTAL$ משימות אבטחה הושלמו", "placeholders": { "count": { "content": "$1", @@ -85,28 +88,28 @@ } }, "passwordChangeProgress": { - "message": "Password change progress" + "message": "התקדמות שינוי סיסמה" }, "assignMembersTasksToMonitorProgress": { - "message": "Assign members tasks to monitor progress" + "message": "הקצה משימות לחברים כדי לנטר התקדמות" }, "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "message": "לאחר שתסקור יישומים ותסמן אותם כקריטיים, באפשרותך להקצות משימות לחברים כדי לפתור פריטים בסיכון ולנטר התקדמות כאן" }, "sendReminders": { - "message": "Send reminders" + "message": "שלח תזכורות" }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { - "message": "Once you mark applications critical, they will display here." + "message": "לאחר שתסמן יישומים כקריטיים, הם יופיעו כאן." }, "viewAtRiskMembers": { - "message": "View at-risk members" + "message": "הצג חברים בסיכון" }, "viewAtRiskApplications": { - "message": "View at-risk applications" + "message": "הצג יישומים בסיכון" }, "criticalApplicationsAreAtRisk": { - "message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords", + "message": "$COUNT$ מתוך $TOTAL$ יישומים קריטיים הם בסיכון בשל סיסמאות בסיכון", "placeholders": { "count": { "content": "$1", @@ -127,8 +130,11 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { - "message": "$COUNT$ critical applications", + "message": "$COUNT$ יישומים קריטיים", "placeholders": { "count": { "content": "$1", @@ -137,7 +143,7 @@ } }, "countOfApplicationsAtRisk": { - "message": "$COUNT$ applications at-risk", + "message": "$COUNT$ יישומים בסיכון", "placeholders": { "count": { "content": "$1", @@ -146,7 +152,7 @@ } }, "countOfAtRiskPasswords": { - "message": "$COUNT$ passwords at-risk", + "message": "$COUNT$ סיסמאות בסיכון", "placeholders": { "count": { "content": "$1", @@ -155,7 +161,7 @@ } }, "newPasswordsAtRisk": { - "message": "$COUNT$ new passwords at-risk", + "message": "$COUNT$ סיסמאות חדשות בסיכון", "placeholders": { "count": { "content": "$1", @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "לא נמצאו יישומים עבור $ORG NAME", "placeholders": { "org name": { "content": "$1", @@ -182,37 +188,37 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "ייבא את נתוני הכניסה של הארגון שלך כדי להתחיל בניטור סיכוני אבטחה של אישורים. לאחר הייבוא תוכל:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "לתעדף סיכונים" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "התמקדות ביישומים החשובים ביותר" }, "benefit2Title": { - "message": "Guide remediation" + "message": "להדריך תיקון" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "הקצה משימות מודרכות לחברים בסיכון כדי להחליף אישורים בסיכון" }, "benefit3Title": { - "message": "Monitor progress" + "message": "לנטר התקדמות" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "עקוב אחר שינויים לאורך זמן כדי להציג שיפורי אבטחה" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "הרץ את הדוח הראשון שלך כדי לראות יישומים" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "ייצר דוח תובנות סיכון כדי לנתח את היישומים של הארגון שלך ולזהות סיסמאות בסיכון שצריכות תשומת לב. הרצת הדוח הראשון שלך תגרום ל:" }, "noCriticalApplicationsTitle": { "message": "לא סימנת אף יישום כקריטי" }, "noCriticalApplicationsDescription": { - "message": "בחר את האפליקציות הקריטיות ביותר שלך כדי לתעדף פעולות אבטחה עבור המשתמשים שלך כדי לטפל בסיסמאות בסיכון." + "message": "בחר את היישומים הקריטיים ביותר שלך כדי לתעדף פעולות אבטחה עבור המשתמשים שלך על מנת לטפל בסיסמאות בסיכון." }, "markCriticalApplications": { "message": "בחר יישומים קריטיים" @@ -221,22 +227,31 @@ "message": "סמן יישום כקריטי" }, "markAsCritical": { - "message": "Mark as critical" + "message": "סמן כקריטי" }, "applicationsSelected": { - "message": "applications selected" + "message": "יישומים נבחרו" }, "selectApplication": { - "message": "Select application" + "message": "בחר יישום" }, "unselectApplication": { - "message": "Unselect application" + "message": "בטל בחירת יישום" }, "applicationsMarkedAsCriticalSuccess": { "message": "יישומים המסומנים כקריטיים" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { - "message": "Failed to mark applications as critical" + "message": "סימון יישומים כקריטיים נכשל" }, "application": { "message": "יישום" @@ -257,10 +272,16 @@ "message": "חברים בסיכון" }, "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "message": "חברים עם גישה לפריטים בסיכון עבור יישומים קריטיים" + }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." }, "membersAtRiskCount": { - "message": "$COUNT$ members at-risk", + "message": "$COUNT$ חברים בסיכון", "placeholders": { "count": { "content": "$1", @@ -326,10 +347,10 @@ "message": "סה\"כ יישומים" }, "applicationsNeedingReview": { - "message": "Applications needing review" + "message": "יישומים צריכים סקירה" }, "newApplicationsWithCount": { - "message": "$COUNT$ new applications", + "message": "$COUNT$ יישומים חדשים", "placeholders": { "count": { "content": "$1", @@ -338,46 +359,52 @@ } }, "newApplicationsDescription": { - "message": "Review new applications to mark as critical and keep your organization secure" + "message": "סקור יישומים חדשים לסימון כקריטיים ושמור על אבטחת הארגון שלך" }, "reviewNow": { - "message": "Review now" + "message": "סקור עכשיו" + }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" }, "prioritizeCriticalApplications": { - "message": "Prioritize critical applications" + "message": "תעדוף יישומים קריטיים" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { - "message": "Mark as critical functionality will be implemented in a future update" + "message": "שימושיות 'סמן כקריטי' תיושם בעדכון עתידי" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "סקירת היישום נשמרה" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "יישומים חדשים נסקרו" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "שגיאה בשמירת מצב סקירה" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "נא לנסות שוב" }, "unmarkAsCritical": { "message": "בטל סימון כקריטי" }, "criticalApplicationUnmarkedSuccessfully": { - "message": "ביטול הסימון של האפליקציה כקריטית בוצע בהצלחה" + "message": "בוטל בהצלחה הסימון של היישום כקריטי" }, "whatTypeOfItem": { "message": "מאיזה סוג פריט זה?" @@ -835,6 +862,9 @@ "favorites": { "message": "מועדפים" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "סוגים" }, @@ -932,79 +962,79 @@ "message": "הצג פריט" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "כניסה חדשה", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "כרטיס חדש", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "זהות חדשה", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "הערה חדשה", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "מפתח SSH חדש", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "סֵנְד של טקסט חדש", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "סֵנְד של קובץ חדש", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "ערוך כניסה", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "ערוך כרטיס", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "ערוך זהות", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "ערוך הערה", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "ערוך מפתח SSH", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "ערוך סֵנְד של טקסט", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "ערוך סֵנְד של קובץ", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "הצג כניסה", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "הצג כרטיס", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "הצג זהות", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "הצג הערה", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "הצג מפתח SSH", "description": "Header for view SSH key item type" }, "new": { @@ -1348,7 +1378,7 @@ "message": "השאר חלון זה פתוח ועקוב אחר ההנחיות מהדפדפן שלך." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed. Please try again." + "message": "אימות מפתח גישה נכשל. נא לנסות שוב." }, "useADifferentLogInMethod": { "message": "השתמש בשיטת כניסה אחרת" @@ -1360,7 +1390,7 @@ "message": "השתמש בכניסה יחידה" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "הארגון שלך דורש כניסה יחידה." }, "welcomeBack": { "message": "ברוך שובך" @@ -1648,7 +1678,7 @@ "message": "סיסמה ראשית שגויה" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "סיסמה ראשית אינה תקינה. יש לאשר שהדוא\"ל שלך נכון ושהחשבון שלך נוצר ב־$HOST$.", "placeholders": { "host": { "content": "$1", @@ -1666,28 +1696,28 @@ "message": "אין פריטים להצגה ברשימה." }, "noItemsInTrash": { - "message": "No items in trash" + "message": "אין פריטים באשפה" }, "noItemsInTrashDesc": { - "message": "Items you delete will appear here and be permanently deleted after 30 days" + "message": "פריטים שאתה מוחק יופיעו כאן ויימחקו לצמיתות לאחר 30 יום" }, "noItemsInVault": { - "message": "No items in the vault" + "message": "אין פריטים בכספת" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "הכספת מגנה על יותר מרק הסיסמאות שלך. אחסן כניסות מאובטחות, זהויות, כרטיסים והערות באופן מאובטח כאן." }, "emptyFavorites": { - "message": "You haven't favorited any items" + "message": "לא הוספת למועדפים פריטים כלשהם" }, "emptyFavoritesDesc": { - "message": "Add frequently used items to favorites for quick access." + "message": "הוסף פריטים בשימוש תכוף למועדפים עבור גישה מהירה." }, "noSearchResults": { - "message": "No search results returned" + "message": "לא הוחזרו תוצאות חיפוש" }, "clearFiltersOrTryAnother": { - "message": "Clear filters or try another search term" + "message": "נקה מסננים או נסה מונח חיפוש אחר" }, "noPermissionToViewAllCollectionItems": { "message": "אין לך הרשאה להציג את כל הפריטים באוסף זה." @@ -3002,7 +3032,7 @@ "message": "אנא ודא כי יש בחשבונך מספיק קרדיט עבור רכישה זו. אם בחשבונך אין די קרדיט, נשתמש בשיטת התשלום המועדפת בחשבונך כדי לגבות את הפער. באפשרותך להוסיף קרדיט לחשבונך דרך עמוד החיוב." }, "notEnoughAccountCredit": { - "message": "You do not have enough account credit for this purchase. You can add credit to your account from the Billing page." + "message": "אין לך מספיק אשראי בחשבון עבורו רכישה זו. באפשרותך להוסיף אשראי לחשבון שלך מדף החיוב." }, "creditAppliedDesc": { "message": "ניתן להשתמש בקרדיט שבחשבונך כדי לבצע רכישות. נשתמש בקרדיט הראשון הזמין עבור חשבוניות בחשבון זה." @@ -4409,11 +4439,11 @@ "updateBrowser": { "message": "עדכן דפדפן" }, - "generatingYourRiskInsights": { - "message": "מייצר את תובנות הסיכון שלך..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { - "message": "Run report" + "message": "הרץ דוח" }, "updateBrowserDesc": { "message": "אתה משתמש בדפדפן אינטרנט שאיננו נתמך. כספת הרשת עלולה שלא לפעול כראוי." @@ -4498,7 +4528,7 @@ "message": "ההזמנה התקבלה" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "קיבל בהצלחה את ההזמנה שלך." }, "inviteInitAcceptedDesc": { "message": "אתה יכול עכשיו לגשת אל ארגון זה." @@ -4979,13 +5009,13 @@ "description": "ex. Date this password was updated" }, "organizationIsDisabled": { - "message": "הארגון הושעה" + "message": "ארגון מושעה" }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "הארגון מושעה" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "לא ניתן לגשת לפריטים בארגון מושעה. פנה אל בעל הארגון שלך עבור סיוע." }, "secretsAccessSuspended": { "message": "לא ניתן לגשת אל ארגונים מושעים. נא לפנות לבעל הארגון שלך עבור סיוע." @@ -5373,11 +5403,11 @@ "message": "מזהה SSO" }, "ssoIdentifierHint": { - "message": "Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. ", + "message": "ספק את המזהה הזה לחברים שלך כדי שיכנסו באמצעות SSO. חברים יכולים לדלג על הזנת מזהה זה במהלך SSO אם הוגדר דומיין שנדרש. ", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "claimedDomainsLearnMore": { - "message": "Learn more", + "message": "למד עוד", "description": "This will be used as part of a larger sentence, broken up to include a link. The full sentence will read 'Provide this ID to your members to login with SSO. Members can skip entering this identifier during SSO if a claimed domain is set up. Learn more'" }, "unlinkSso": { @@ -5414,10 +5444,10 @@ "message": "תנאים מקדימים" }, "requireSsoPolicyReq": { - "message": "יש להפעיל את המדיניות הארגונית של הארגון היחיד לפני הפעלת מדיניות זו." + "message": "יש להפעיל את המדיניות הארגונית של הארגון היחידי לפני הפעלת מדיניות זו." }, "requireSsoPolicyReqError": { - "message": "מדיניות ארגון יחיד לא הוגדרה." + "message": "מדיניות ארגון יחידי לא הוגדרה." }, "requireSsoExemption": { "message": "מנהלי ובעלי הארגון פטורים מהאכיפה של מדיניות זו." @@ -5762,63 +5792,63 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" }, "availableNow": { - "message": "Available now" + "message": "זמין עכשיו" }, "autoConfirm": { - "message": "Automatic confirmation of new users" + "message": "אישור אוטומטי של משתמשים חדשים" }, "autoConfirmDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked.", + "message": "משתמשים חדשים המוזמנים לארגון יאושרו באופן אוטומטי כאשר מכשיר של מנהל נפתח.", "description": "This is the description of the policy as it appears in the 'Policies' page" }, "howToTurnOnAutoConfirm": { - "message": "How to turn on automatic user confirmation" + "message": "איך להפעיל אישור משתמש אוטומטי" }, "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "message": "פתח את הרחבת Bitwarden שלך." }, "autoConfirmStep2a": { - "message": "Select", + "message": "בחר", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" }, "autoConfirmStep2b": { - "message": " Turn on.", + "message": " הפעל.", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "הרחבת Bitwarden לדפדפן נפתחה בהצלחה. כעת ביכולתך להפעיל את הגדרת אישור אוטומטי של משתמשים." }, "autoConfirmPolicyEditDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked. Before turning on this policy, please review and agree to the following: ", + "message": "משתמשים חדשים המוזמנים לארגון יאושרו באופן אוטומטי כאשר מכשיר של מנהל נפתח. לפני הפעלת מדיניות זו, נא לסקור ולהסכים לדברים הבאים: ", "description": "This is the description of the policy as it appears inside the policy edit dialog" }, "autoConfirmAcceptSecurityRiskTitle": { - "message": "Potential security risk. " + "message": "סיכון אבטחה פוטנציאלי. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "אישור אוטומטי של משתמשים עלול להוות סיכון אבטחה לנתוני הארגון שלך." }, "autoConfirmAcceptSecurityRiskLearnMore": { - "message": "Learn about the risks", + "message": "למד על הסיכונים", "description": "The is the link copy for the first check box option in the edit policy dialog" }, "autoConfirmSingleOrgRequired": { - "message": "Single organization policy required. " + "message": "נדרשת מדיניות ארגון יחידי. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "מדיניות ארגון יחידי תורחב לכל התפקידים. " }, "autoConfirmNoEmergencyAccess": { - "message": "No emergency access. " + "message": "אין גישת חירום. " }, "autoConfirmNoEmergencyAccessDescription": { - "message": "Emergency Access will be removed." + "message": "גישת חירום תוסר." }, "autoConfirmCheckBoxLabel": { - "message": "I accept these risks and policy updates" + "message": "אני מסכים לסיכונים ועדכוני מדיניות אלה" }, "personalOwnership": { "message": "הסר כספת אישית" @@ -5833,10 +5863,10 @@ "message": "בשל מדיניות ארגונית, אתה מוגבל מלשמור פריטים לכספת האישית שלך. שנה את אפשרות הבעלות לארגון ובחר מאוספים זמינים." }, "desktopAutotypePolicy": { - "message": "Desktop Autotype Default Setting" + "message": "הגדרת ברירת מחדל עבור הקלדה אוטומטית בשולחן עבודה" }, "desktopAutotypePolicyDesc": { - "message": "Turn Desktop Autotype ON by default for members. Members can turn Autotype off manually in the Desktop client.", + "message": "הפעל הקלדה אוטומטית בשולחן עבודה כברירת מחדל עבור חברים. חברים יכולים להשבית הקלדה אוטומטית באופן ידני בלקוח שולחן העבודה.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -5872,6 +5902,19 @@ "message": "הצג תמיד את כתובת הדוא\"ל של חבר מנמענים בעת יצירת או עריכת סֵנְד.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "מדיניות $ID$ שונתה.", "placeholders": { @@ -6252,7 +6295,7 @@ "message": "חברים בעלי חשבונות קיימים עם סיסמאות ראשיות יידרשו להירשם בעצמם לפני שמנהלים יוכלו לשחזר את החשבונות שלהם. הרשמה אוטומטית תפעיל שחזור חשבון עבור חברים חדשים." }, "accountRecoverySingleOrgRequirementDesc": { - "message": "יש להפעיל את המדיניות הארגונית של הארגון היחיד לפני הפעלת מדיניות זו." + "message": "יש להפעיל את המדיניות הארגונית של הארגון היחידי לפני הפעלת מדיניות זו." }, "resetPasswordPolicyAutoEnroll": { "message": "הרשמה אוטומטית" @@ -6345,7 +6388,7 @@ "message": "חברים שאינם עומדים בדרישות" }, "nonCompliantMembersError": { - "message": "חברים שאינם עומדים בדרישות מדיניות הארגון היחיד או הכניסה הדו־שלבית אינם ניתנים לשחזור עד שהם יעמדו בדרישות המדיניות" + "message": "חברים שאינם עומדים בדרישות מדיניות הארגון היחידי או הכניסה הדו־שלבית אינם ניתנים לשחזור עד שהם יעמדו בדרישות המדיניות" }, "fingerprint": { "message": "טביעת אצבע" @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "הסיסמה הראשית שלך אינה עומדת באחת או יותר מפוליסות הארגון שלך. כדי לגשת לכספת, אתה מוכרח לעדכן את הסיסמה הראשית שלך עכשיו. בהמשך תנותק מההפעלה הנוכחית שלך, מה שידרוש ממך להיכנס חזרה. הפעלות פעילות במכשירים אחרים עלולות להישאר פעילות למשך עד שעה אחת." }, - "automaticAppLogin": { - "message": "הכנס באופן אוטומטי משתמשים עבור יישומים מותרים" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "טפסי כניסה ימולאו ויוגשו באופן אוטומטי עבור יישומים שנפתחו מספק הזהות המוגדר שלך." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "מארח ספק זהות" @@ -6541,31 +6584,31 @@ "message": "הארגון שלך עדכן את אפשרויות הפענוח שלך. נא להגדיר סיסמה ראשית כדי לגשת לכספת שלך." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "פסק זמן להפעלה" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "הגדר פסק זמן להפעלה מרבי עבור כל החברים חוץ מבעלים." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "פסק הזמן המרבי המותר" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "נדרש פסק הזמן המרבי המותר." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "הזמן אינו חוקי. שנה לפחות ערך אחד." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "פעולת פסק זמן להפעלה" }, "immediately": { - "message": "Immediately" + "message": "באופן מיידי" }, "onSystemLock": { - "message": "On system lock" + "message": "בנעילת המערכת" }, "onAppRestart": { - "message": "On app restart" + "message": "בהפעלת היישום מחדש" }, "hours": { "message": "שעות" @@ -6574,19 +6617,19 @@ "message": "דקות" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "האם אתה בטוח שברצונך להתיר פסק זמן מרבי של \"לעולם לא\" עבור כל החברים?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "אפשרות זו תשמור את מפתחות ההצפנה של החברים שלך במכשירים שלהם. אם אתה בוחר אפשרות זו, תוודא שהמכשירים שלהם מוגנים במידה מספקת." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "למד עוד על הגנת מכשיר" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"נעילת מערכת\" חלה רק על היישום לדפדפן ולשולחן העבודה" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "היישום לנייד וברשת ישתמש באפשרות \"בהפעלת היישום מחדש\" בתור פסק הזמן המרבי המותר, מאחר והאפשרות אינה נתמכת." }, "vaultTimeoutPolicyInEffect": { "message": "פוליסות הארגון שלך הגדירו את פסק הזמן לכספת המרבי שלך ל־$HOURS$ שעות ו־$MINUTES$ דקות.", @@ -7034,7 +7077,7 @@ "message": "SSO כבוי" }, "emailMustLoginWithSso": { - "message": "$EMAIL$ must login with Single Sign-on", + "message": "$EMAIL$ מוכרח להיכנס עם כניסה יחידה", "placeholders": { "email": { "content": "$1", @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, + "selfHostedEnvMustUseHttps": { + "message": "כתובות URL מוכרחות להשתמש ב־HTTPS." + }, "apiUrl": { "message": "URL של שרת ה־API" }, @@ -7281,7 +7327,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא.", "placeholders": { "organization": { "content": "$1", @@ -7290,7 +7336,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא. פריטי האוספים שלי לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "גישה נדחתה. אין לך הרשאות כדי לצפות בעמוד זה." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "סיסמה ראשית" }, @@ -7453,7 +7502,7 @@ "message": "סוד לא ידוע, ייתכן שאתה צריך לבקש הרשאה כדי לגשת אל סוד זה." }, "unknownServiceAccount": { - "message": "Unknown machine account, you may need to request permission to access this machine account." + "message": "חשבון מכונה לא ידוע, ייתכן שתצטרך לבקש הרשאה לגשת לחשבון מכונה זה." }, "unknownProject": { "message": "פרויקט לא ידוע, ייתכן שאתה צריך לבקש הרשאה כדי לגשת אל פרויקט זה." @@ -8806,7 +8855,7 @@ } }, "accessedProjectWithIdentifier": { - "message": "Accessed a project with identifier: $PROJECT_ID$.", + "message": "ניגש לפרויקט עם מזהה: $PROJECT_ID$.", "placeholders": { "project_id": { "content": "$1", @@ -8833,7 +8882,7 @@ } }, "nameUnavailableServiceAccountDeleted": { - "message": "Deleted machine account Id: $SERVICE_ACCOUNT_ID$", + "message": "מזהה חשבון המכונה שנמחק: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8851,7 +8900,7 @@ } }, "addedUserToServiceAccountWithId": { - "message": "Added user: $USER_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "הוסיף משתמש: $USER_ID$ לחשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -8864,7 +8913,7 @@ } }, "removedUserToServiceAccountWithId": { - "message": "Removed user: $USER_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "הסיר משתמש: $USER_ID$ מחשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "user_id": { "content": "$1", @@ -8877,7 +8926,7 @@ } }, "removedGroupFromServiceAccountWithId": { - "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "הסיר קבוצה: $GROUP_ID$ מחשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "group_id": { "content": "$1", @@ -8890,7 +8939,7 @@ } }, "serviceAccountCreatedWithId": { - "message": "Created machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "יצר חשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -8899,7 +8948,7 @@ } }, "addedGroupToServiceAccountId": { - "message": "Added group: $GROUP_ID$ to machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "הוסיף קבוצה: $GROUP_ID$ לחשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "group_id": { "content": "$1", @@ -8912,7 +8961,7 @@ } }, "serviceAccountDeletedWithId": { - "message": "Deleted machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "מחק חשבון מכונה עם מזהה: $SERVICE_ACCOUNT_ID$", "placeholders": { "service_account_id": { "content": "$1", @@ -9127,7 +9176,7 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { - "message": "מדיניות הארגון היחיד", + "message": "מדיניות הארגון היחידי", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescPart2": { @@ -9430,20 +9479,20 @@ "message": "הגדר את התנהגות האוספים עבור הארגון" }, "allowAdminAccessToAllCollectionItemsDescription": { - "message": "אפשר לבעלים ומנהלים לנהל את כל האוספים והפריטים ממסוף המנהל" + "message": "אפשר לבעלים ולמנהלים לנהל את כל האוספים והפריטים ממסוף המנהל" }, "restrictCollectionCreationDescription": { - "message": "הגבל יצירת אוספים לבעלים ומנהלים" + "message": "הגבל יצירת אוספים לבעלים ולמנהלים" }, "restrictCollectionDeletionDescription": { - "message": "הגבל מחיקת אוספים לבעלים ומנהלים" + "message": "הגבל מחיקת אוספים לבעלים ולמנהלים" }, "restrictItemDeletionDescriptionStart": { - "message": "Restrict item deletion to members with the ", + "message": "הגבל מחיקת פריטים לחברים עם הרשאת ", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "restrictItemDeletionDescriptionEnd": { - "message": " permission", + "message": "​", "description": "This will be used as part of a larger sentence, broken up to allow styling of the middle portion. Full sentence: 'Restrict item deletion to members with the [Manage collection] permission'" }, "updatedCollectionManagement": { @@ -9577,7 +9626,7 @@ } }, "allowAdminAccessToAllCollectionItemsEnabled": { - "message": "הופעלה הגדרת הרשה לבעלים ומנהלים לנהל את כל האוספים והפריטים $ID$.", + "message": "הופעלה הגדרת 'אפשר לבעלים ולמנהלים לנהל את כל האוספים והפריטים' $ID$.", "placeholders": { "id": { "content": "$1", @@ -9586,7 +9635,7 @@ } }, "allowAdminAccessToAllCollectionItemsDisabled": { - "message": "כובתה הגדרת הרשה לבעלים ומנהלים לנהל את כל האוספים והפריטים $ID$.", + "message": "כובתה הגדרת 'אפשר לבעלים ולמנהלים לנהל את כל האוספים והפריטים' $ID$.", "placeholders": { "id": { "content": "$1", @@ -9757,7 +9806,10 @@ "message": "הקצה" }, "assignTasks": { - "message": "Assign tasks" + "message": "הקצה משימות" + }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" }, "assignToCollections": { "message": "הקצה לאוספים" @@ -10155,16 +10207,16 @@ "message": "שלח נתוני אירועים אל מופע ה־Logscale שלך" }, "datadogEventIntegrationDesc": { - "message": "Send vault event data to your Datadog instance" + "message": "שלח נתוני אירועי כספת אל מופע ה־Datadog שלך" }, "failedToSaveIntegration": { "message": "שמירת האינטגרציה נכשלה. נא לנסות שוב מאוחר יותר." }, "mustBeOrgOwnerToPerformAction": { - "message": "You must be the organization owner to perform this action." + "message": "אתה מוכרח להיות מנהל הארגון כדי לבצע פעולה זו." }, "failedToDeleteIntegration": { - "message": "Failed to delete integration. Please try again later." + "message": "מחיקת האינטגרציה נכשלה. נא לנסות שוב מאוחר יותר." }, "deviceIdMissing": { "message": "מזהה המכשיר חסר" @@ -10263,7 +10315,7 @@ "message": "אסימון נושא" }, "repositoryNameHint": { - "message": "Name of the repository to ingest into" + "message": "שם המאגר להטמעה" }, "index": { "message": "אינדקס" @@ -10945,7 +10997,7 @@ "message": "אירוח עצמי" }, "claim-domain-single-org-warning": { - "message": "דרישת דומיין תפעיל את מדיניות הארגון היחיד." + "message": "דרישת דומיין תפעיל את מדיניות הארגון היחידי." }, "single-org-revoked-user-warning": { "message": "חברים שאינם עומדים בדרישות יבוטלו. מנהלים יכולים לשחזר חברים ברגע שהם עזבו את כל הארגונים האחרים." @@ -11035,7 +11087,7 @@ "message": "אל תאפשר לחברים לממש תוכנית משפחות דרך ארגון זה." }, "verifyBankAccountWithStatementDescriptorWarning": { - "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף החיוב של הארגון כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." + "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי העסקים הבאים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף החיוב של הארגון כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "verifyBankAccountWithStatementDescriptorInstructions": { "message": "ביצענו מיקרו־הפקדה לחשבון הבנק שלך (זה עשוי לקחת 1-2 ימי עסקים). הזן את הקוד בן שש הספרות המתחיל ב־'SM' הנמצא בתיאור ההפקדה. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." @@ -11147,13 +11199,13 @@ "message": "דומיין נדרש" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "פריט נוסף למועדפים" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "פריט הוסר מהמועדפים" }, "copyNote": { - "message": "Copy note" + "message": "העתק הערה" }, "organizationNameMaxLength": { "message": "שם ארגון לא יכול לחרוג מ־50 תווים." @@ -11388,10 +11440,10 @@ "message": "שנה סיסמה בסיכון" }, "changeAtRiskPasswordAndAddWebsite": { - "message": "כניסה זו נמצאת בסיכון וחסר בה אתר אינטרנט. הוסף אתר אינטרנט ושנה את הסיסמה לאבטחה חזקה יותר." + "message": "כניסה זו נמצאת בסיכון וחסר לה אתר אינטרנט. הוסף אתר אינטרנט ושנה את הסיסמה עבור אבטחה חזקה יותר." }, "missingWebsite": { - "message": "לא נמצא אתר אינטרנט" + "message": "אתר אינטרנט חסר" }, "removeUnlockWithPinPolicyTitle": { "message": "הסר ביטול נעילה עם PIN" @@ -11415,56 +11467,56 @@ "message": "לארגונים חינמיים יכולים להיות עד 2 אוספים. שדרג לתוכנית בתשלום כדי להוסיף עוד אוספים." }, "searchArchive": { - "message": "Search archive" + "message": "חיפוש בארכיון" }, "archiveNoun": { - "message": "Archive", + "message": "ארכיון", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "העבר לארכיון", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "הסר מהארכיון" }, "itemsInArchive": { - "message": "Items in archive" + "message": "פריטים בארכיון" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "אין פריטים בארכיון" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "הפריט נשלח לארכיון" }, "itemsWereSentToArchive": { - "message": "Items were sent to archive" + "message": "פריטים שנשלחו לארכיון" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "הפריט הוסר מהארכיון" }, "bulkArchiveItems": { - "message": "Items archived" + "message": "הפריטים הועברו לארכיון" }, "bulkUnarchiveItems": { - "message": "Items unarchived" + "message": "הפריטים הוסרו מהארכיון" }, "archiveItem": { - "message": "Archive item", + "message": "העבר פריט לארכיון", "description": "Verb" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריט זה לארכיון?" }, "archiveBulkItems": { - "message": "Archive items", + "message": "העבר פריטים לארכיון", "description": "Verb" }, "archiveBulkItemsConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive these items?" + "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריטים אלה לארכיון?" }, "businessUnit": { "message": "יחידת עסקים" @@ -11574,10 +11626,10 @@ "message": "הרחבת Bitwarden הותקנה!" }, "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" + "message": "פתח את ההרחבה של Bitwarden" }, "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "message": "ההרחבה של Bitwarden הותקנה! פתח את ההרחבה כדי להיכנס ולהתחיל למלא אוטומטית." }, "openExtensionToAutofill": { "message": "פתח את ההרחבה כדי להיכנס ולהתחיל למלא אוטומטית." @@ -11604,7 +11656,7 @@ "message": "הפעל מחדש" }, "verifyProviderBankAccountWithStatementDescriptorWarning": { - "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף המנוי של הספק כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." + "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי העסקים הבאים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף המנוי של הספק כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "clickPayWithPayPal": { "message": "נא ללחוץ על הלחצן 'שלם עם PayPal' כדי להוסיף את שיטת התשלום שלך." @@ -11669,7 +11721,7 @@ "message": "קוד האבטחה של הכרטיס, הידוע גם כ־CVV או CVC, הוא בדרך כלל מספר בן 3 ספרות המודפס על גבי כרטיס האשראי שלך או מספר בן 4 ספרות המודפס מקדימה מעל מספר הכרטיס שלך." }, "verifyBankAccountWarning": { - "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף פרטי התשלום כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." + "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי העסקים הבאים. הזן את קוד המתאר של ההצהרה מהפקדה זו בדף פרטי התשלום כדי לאמת את חשבון הבנק. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "taxId": { "message": "מזהה מס: $TAX_ID$", @@ -11797,43 +11849,43 @@ "message": "אשר דומיין של Key Connector" }, "requiredToVerifyBankAccountWithStripe": { - "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי עסקים. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." + "message": "תשלום באמצעות חשבון בנק זמין רק ללקוחות בארצות הברית. אתה תידרש לאמת את חשבון הבנק שלך. אנחנו נבצע מיקרו־הפקדה בתוך 1-2 ימי העסקים הבאים. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "verifyBankAccountWithStripe": { - "message": "ביצענו מיקרו־הפקדה לחשבון הבנק שלך. זה עשוי לקחת 1-2 ימי עסקים. כאשר תראה את ההפקדה בחשבונך, תוכל לאמת את חשבון הבנק שלך. כשל באימות חשבון הבנק יגרום לפספוס תשלום ולהשעיית המנוי שלך." + "message": "ביצענו מיקרו־הפקדה לחשבון הבנק שלך. זה עשוי לקחת 1-2 ימי עסקים. כאשר תראה את ההפקדה בחשבונך, תוכל לאמת את חשבון הבנק שלך. כשל באימות חשבון הבנק שלך יגרום לפספוס תשלום ולהשעיית המנוי שלך." }, "verifyNow": { "message": "אמת כעת." }, "additionalStorageGB": { - "message": "Additional storage GB" + "message": "אחסון נוסף ב־GB" }, "additionalServiceAccountsV2": { - "message": "Additional machine accounts" + "message": "חשבונות מכונה נוספים" }, "secretsManagerSeats": { - "message": "Secrets Manager seats" + "message": "מקומות במנהל הסודות" }, "additionalStorage": { - "message": "Additional Storage" + "message": "אחסון נוסף" }, "expandPurchaseDetails": { - "message": "Expand purchase details" + "message": "הרחב פריטי רכישה" }, "collapsePurchaseDetails": { - "message": "Collapse purchase details" + "message": "כווץ פריטי רכישה" }, "familiesMembership": { - "message": "Families membership" + "message": "חברות למשפחות" }, "planDescPremium": { - "message": "Complete online security" + "message": "השלם אבטחה מקוונת" }, "planDescFamiliesV2": { - "message": "Premium security for your family" + "message": "אבטחת פרימיום למשפחה שלך" }, "planDescFreeV2": { - "message": "Share with $COUNT$ other user", + "message": "שתף עם משתמש $COUNT$ אחר", "placeholders": { "count": { "content": "$1", @@ -11842,37 +11894,37 @@ } }, "planDescEnterpriseV2": { - "message": "Advanced capabilities for any organization" + "message": "יכולות מתקדמות עבור כל ארגון" }, "planNameCustom": { - "message": "Custom plan" + "message": "תוכנית מותאמת אישית" }, "planDescCustom": { - "message": "Bitwarden scales with businesses of all sizes to secure passwords and sensitive information. If you're part of a large enterprise, contact sales to request a quote." + "message": "Bitwarden מתרחבת עם עסקים בכל הגדלים כדי לאבטח סיסמאות ומידע רגיש. אם אתה חלק מארגון גדול, צור קשר עם המכירות כדי לבקש הצעת מחיר." }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "מאמת מובנה" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "ניטור פרצות" }, "andMoreFeatures": { - "message": "And more!" + "message": "ועוד!" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "אחסון קבצים מאובטח" }, "familiesUnlimitedSharing": { - "message": "Unlimited sharing - choose who sees what" + "message": "שיתוף ללא הגבלה - בחר מי רואה מה" }, "familiesUnlimitedCollections": { - "message": "Unlimited family collections" + "message": "אוספים משפחתיים ללא הגבלה" }, "familiesSharedStorage": { - "message": "Shared storage for important family info" + "message": "אחסון משותף עבור מידע משפחתי חשוב" }, "limitedUsersV2": { - "message": "Up to $COUNT$ members", + "message": "עד $COUNT$ חברים", "placeholders": { "count": { "content": "$1", @@ -11881,7 +11933,7 @@ } }, "limitedCollectionsV2": { - "message": "Up to $COUNT$ collections", + "message": "עד $COUNT$ אוספים", "placeholders": { "count": { "content": "$1", @@ -11890,13 +11942,13 @@ } }, "alwaysFree": { - "message": "Always free" + "message": "תמיד חינם" }, "twoSecretsIncluded": { - "message": "2 secrets" + "message": "2 סודות" }, "projectsIncludedV2": { - "message": "$COUNT$ project(s)", + "message": "$COUNT$ פרויקט(ים)", "placeholders": { "count": { "content": "$1", @@ -11905,13 +11957,13 @@ } }, "secureItemSharing": { - "message": "Secure item sharing" + "message": "שיתוף פריטים מאובטח" }, "scimSupport": { - "message": "SCIM support" + "message": "תמיכת SCIM" }, "includedMachineAccountsV2": { - "message": "$COUNT$ machine accounts", + "message": "$COUNT$ חשבונות מכונה", "placeholders": { "count": { "content": "$1", @@ -11920,108 +11972,108 @@ } }, "enterpriseSecurityPolicies": { - "message": "Enterprise security policies" + "message": "פוליסות אבטחה ארגוניות" }, "selfHostOption": { - "message": "Self-host option" + "message": "אפשרות לאירוח עצמי" }, "complimentaryFamiliesPlan": { - "message": "Complimentary families plan for all users" + "message": "תוכנית למשפחות על חשבון הבית לכל המשתמשים" }, "strengthenCybersecurity": { - "message": "Strengthen cybersecurity" + "message": "חזק את אבטחת הסייבר" }, "boostProductivity": { - "message": "Boost productivity" + "message": "שיפור פרודוקטיביות" }, "seamlessIntegration": { - "message": "Seamless integration" + "message": "אינטגרציה חלקה" }, "families": { - "message": "Families" + "message": "משפחות" }, "upgradeToFamilies": { - "message": "Upgrade to Families" + "message": "שדרג למשפחות" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "שדרג לפרימיום" }, "familiesUpdated": { - "message": "You've upgraded to Families!" + "message": "שדרגת למשפחות!" }, "taxCalculationError": { - "message": "There was an error calculating tax for your location. Please try again." + "message": "אירעה שגיאה בחישוב המס עבור המיקום שלך. נא לנסות שוב." }, "individualUpgradeWelcomeMessage": { - "message": "Welcome to Bitwarden" + "message": "ברוך בואך אל Bitwarden" }, "individualUpgradeDescriptionMessage": { - "message": "Unlock more security features with Premium, or start sharing items with Families" + "message": "פתח תכונות אבטחה נוספות עם פרימיום, או התחל בשיתוף פריטים עם משפחות" }, "individualUpgradeTaxInformationMessage": { - "message": "Prices exclude tax and are billed annually." + "message": "המחירים אינם כוללים מס ומחויבים מדי שנה." }, "organizationNameDescription": { - "message": "Your organization name will appear in invitations you send to members." + "message": "שם הארגון שלך יופיע בהזמנות שאתה שולח לחברים." }, "continueWithoutUpgrading": { - "message": "Continue without upgrading" + "message": "המשך ללא שדרוג" }, "upgradeYourPlan": { - "message": "Upgrade your plan" + "message": "שדרג את התוכנית שלך" }, "upgradeNow": { - "message": "Upgrade now" + "message": "שדרג עכשיו" }, "formWillCreateNewFamiliesOrgMessage": { - "message": "Completing this form will create a new Families organization. You can upgrade your Free organization from the Admin Console." + "message": "השלמת טופס זה תיצור ארגון משפחות חדש. באפשרותך לשדרג את הארגון החינמי שלך ממסוף המנהל." }, "upgradeErrorMessage": { - "message": "We encountered an error while processing your upgrade. Please try again." + "message": "נתקלנו בשגיאה בעת עיבוד השדרוג שלך. נא לנסות שוב." }, "bitwardenFreeplanMessage": { - "message": "You have the Bitwarden Free plan" + "message": "יש לך תוכנית Bitwarden בחינם" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "שדרג עבור אבטחה מלאה" }, "viewbusinessplans": { - "message": "View business plans" + "message": "הצג תוכניות עסקיות" }, "updateEncryptionSettings": { - "message": "Update encryption settings" + "message": "עדכן הגדרות הצפנה" }, "updateYourEncryptionSettings": { - "message": "Update your encryption settings" + "message": "עדכן את הגדרות ההצפנה שלך" }, "updateSettings": { - "message": "Update settings" + "message": "עדכן הגדרות" }, "algorithm": { - "message": "Algorithm" + "message": "אלגוריתם" }, "encryptionKeySettingsHowShouldWeEncryptYourData": { - "message": "Choose how Bitwarden should encrypt your vault data. All options are secure, but stronger methods offer better protection - especially against brute-force attacks. Bitwarden recommends the default setting for most users." + "message": "בחר כיצד על Bitwarden להצפין את נתוני הכספת שלך. כל האפשרויות בטוחות, אבל שיטות חזקות יותר מציעות הגנה טובה יותר - במיוחד נגד מתקפות כוחניות. Bitwarden ממליצה על הגדרת ברירת המחדל עבור רוב המשתמשים." }, "encryptionKeySettingsIncreaseImproveSecurity": { - "message": "Increasing the values above the default will improve security, but your vault may take longer to unlock as a result." + "message": "שיפור הערכים מעל ברירת המחדל ישפרו אבטחה, אבל לכספת שלך עשוי לקחת יותר זמן להיפתח כתוצאה." }, "encryptionKeySettingsAlgorithmPopoverTitle": { - "message": "About encryption algorithms" + "message": "אודות אלגוריתמי הצפנה" }, "encryptionKeySettingsAlgorithmPopoverPBKDF2": { - "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." + "message": "האלגוריתם PBKDF2-SHA256 הוא שיטת הצפנה בדוקה היטב אשר מאזנת אבטחה וביצועים. טוב עבור רוב המשתמשים." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "האלגוריתם Argon2id מציע הגנה חזקה יותר נגד מתקפות מודרניות. טוב ביותר עבור משתמשים מתקדמים עם מכשירים חזקים." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "מיקוד" }, "cardNumberLabel": { - "message": "Card number" + "message": "מספר כרטיס" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "התחל ניסיון משפחות בחינם" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 4744fe42487..98d9896dd00 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "पसंदीदा" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "प्रकार" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index c95eb0a5075..e97c0bdb040 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Pristup inteligenciji" }, - "riskInsights": { - "message": "Uvid u rizik" - }, "passwordRisk": { "message": "Rizik lozinke" }, + "noEditPermissions": { + "message": "Nemaš dozvolu za uređivanje ove stavke" + }, "reviewAtRiskPasswords": { "message": "Pregledaj rizične lozinke (slabe, izložene ili ponovno korištene) u svim aplikacijama. Odaberi svoje najkritičnije aplikacije za davanje prioriteta sigurnosnim radnjama da tvoji korisnici riješe rizične lozinke." }, + "reviewAtRiskLoginsPrompt": { + "message": "Pregledaj rizične prijave" + }, "dataLastUpdated": { "message": "Zadnje ažuriranje: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "kritičnih aplikacija označeno" + }, "countOfCriticalApplications": { "message": "Kritičnih aplikacija: $COUNT$", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Nisu nađene aplikacije u $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Uvezi podatke za prijavu svoje organizacije za početak praćenja sigurnosnih rizika vjerodajnica. Nakon uvoza možeš:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Prioritizirajte rizike" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Usredotoči se na najvažnije aplikacije" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Vodič za sanaciju" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Dodijeli vođene zadatke rizičnim članovima za rotaciju rizičnih vjerodajnica" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Prati napredak" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Prati promjene tijekom vremena za prikaz sigurnosnih poboljšanja" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Za pregled aplikacija, pokreni svoje prvo izvješće" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Generiraj izvješće o uvidima u rizike za analizu aplikacije svoje organizacije i identifikaciju lozinki koje su u riziku i kojima je potrebna pozornost. Pokretanje tvojeg prvog izvješća će:" }, "noCriticalApplicationsTitle": { "message": "Niti jedna aplikacija nije označena kao kritična" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplikacije označene kao kritične" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ aplikacija označeno kao kritično", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Nije uspjelo označavanje aplikacija kao kritičnih" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Članovi koji imaju pristup stavkama za aplikacije označene kao kritične" }, + "membersWithAtRiskPasswords": { + "message": "Članovi s rizičnim lozinkama" + }, + "membersWillReceiveNotification": { + "message": "Članovi će dobiti obavijest o rješavanju rizičnih prijava putem proširenja preglednika." + }, "membersAtRiskCount": { "message": "Rizičnih članova: $COUNT$", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Pregledaj sada" }, + "allCaughtUp": { + "message": "Sve ažurno!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Trenutno nema novih aplikacija za pregled" + }, "prioritizeCriticalApplications": { "message": "Daj prioritet kritičnim aplikacijama" }, - "atRiskItems": { - "message": "Rizične stavke" + "selectCriticalApplicationsDescription": { + "message": "Odaberi koje su aplikacije najvažnije za tvoju organizaciju, a zatim dodijeli sigurnosne zadatke članovima kako bi se riješili rizici." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Klikni ikonu zvjezdice za označavanje aplikacije kao kritične" }, "markAsCriticalPlaceholder": { "message": "Funkcionalnost „Označi kao kritično” bit će implementirana u budućem ažuriranju" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Provjera aplikacija spremljena" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Nove aplikacije pregledane" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Pogreška pri spremanju statusa pregleda" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Molimo pokušaj ponovno" }, "unmarkAsCritical": { "message": "Odznači kao kritično" @@ -835,6 +862,9 @@ "favorites": { "message": "Favoriti" }, + "taskSummary": { + "message": "Sažetak zadatka" + }, "types": { "message": "Vrste" }, @@ -1360,7 +1390,7 @@ "message": "Jedinstvena prijava (SSO)" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tvoje organizacija zahtijeva jedinstvenu prijavu." }, "welcomeBack": { "message": "Dobro došli natrag" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Ažuriraj preglednik" }, - "generatingYourRiskInsights": { - "message": "Stvaranje tvojih uvida u rizik..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Pokreni izvješće" @@ -4498,7 +4528,7 @@ "message": "Poziv prihvaćen" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Tvoj poziv uspješno prihvaćen." }, "inviteInitAcceptedDesc": { "message": "Sada imaš pristup organizaciji." @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Potrebno je pravilo Isključive organizacije. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Svakome tko je dio više od jedne organizacije bit će ukinut pristup sve dok ne napusti ostale organizacije." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Svi članovi moraju pripadati samo ovoj organizaciji kako bi aktivirali ovu automatizaciju." }, "autoConfirmSingleOrgExemption": { "message": "Pravilo Isključive organizacija će biti primijenjeno na sve uloge." @@ -5872,6 +5902,19 @@ "message": "Ne dopusti skrivanje e-pošte kod stvaranja Senda.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Zadano otkrivanje podudaranja URI-ja" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Odredi kada se prijave predlažu za auto-ispunu. Administratori i vlasnici su izuzeti od ovog pravila." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Zadano otkrivanje podudaranja URI-ja" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Odaberite valjanu opciju otkrivanja podudaranja URI-ja.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Izmijenjena polica $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Tvoja glavna lozinka ne zadovoljava pravila ove organizacije. Za pristup trezoru moraš odmah ažurirati svoju glavnu lozinku. Ako nastaviš, odjaviti ćeš se iz trenutne sesije te ćeš se morati ponovno prijaviti. Aktivne sesije na drugim uređajima mogu ostati aktivne do jedan sat." }, - "automaticAppLogin": { - "message": "Automatski prijavi korisnike za dopuštene aplikacije" + "automaticAppLoginWithSSO": { + "message": "Automatska jedinstvena prijava" }, - "automaticAppLoginDesc": { - "message": "Obrasci za prijavu će biti automatski ispunjeni i poslani za aplikacije pokrenute od strane konfiguriranog davatelja identiteta." + "automaticAppLoginWithSSODesc": { + "message": "Proširi sigurnost i praktičnost SSO-a na neupravljane aplikacije. Kada korisnici pokrenu aplikaciju vašeg davatelja identiteta, njihovi se podaci za prijavu automatski popunjavaju i šalju, stvarajući siguran tijek od davatelja identiteta do aplikacije jednim klikom." }, "automaticAppLoginIdpHostLabel": { "message": "Poslužitelj pružatelja identiteta" @@ -6541,31 +6584,31 @@ "message": "Tvoja je organizacija ažurirala tvoje opcije dešifriranja. Postavi glavnu lozinku za pristup svom trezoru." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Istek sesije" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Odredi najveće vrijeme isteka sesije za sve članove, osim vlasnika." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Najveći istek trezora" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Potreban najveći dozvoljeni istek." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Vrijeme nije ispravno. Promijeni barem jednu vrijednost." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Radnja kod isteka sesije" }, "immediately": { - "message": "Immediately" + "message": "Odmah" }, "onSystemLock": { - "message": "On system lock" + "message": "Pri zaključavanju sustava" }, "onAppRestart": { - "message": "On app restart" + "message": "Pri ponovnom pokretanju" }, "hours": { "message": "sat(i)" @@ -6574,19 +6617,19 @@ "message": "min." }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Sigurno želiš odrediti „Nikada” kao najveći istek za sve članove?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Ova će opcija spremiti ključeve za šifriranje tvojih članova na njihove uređaje. Prije uključivanja ove opcije, provjeri jesu li njihovi uređaji adekvatno zaštićeni." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Saznaj više o zaštiti uređaja" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "„Zaključavanje sustava” će se odnositi samo na aplikaciju radne površine i proširenje" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Mobilna i web aplikacija koristiti će „pri ponovnom pokretanju” kao najveći dozvoljeni istek, jer opcija još nije podržana." }, "vaultTimeoutPolicyInEffect": { "message": "Pravilo tvoje organizacije utječe na istek trezora. Najveće dozvoljeno vrijeme isteka je $HOURS$:$MINUTES$ h.", @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Moraš dodati ili osnovni URL poslužitelja ili barem jedno prilagođeno okruženje." }, + "selfHostedEnvMustUseHttps": { + "message": "URL mora koristiti HTTPS." + }, "apiUrl": { "message": "URL API poslužitelja" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Pristup odbijen. Nemaš prava vijdeti ovu stranicu." }, + "noPageAccess": { + "message": "Nemaš pristup ovoj stranici" + }, "masterPassword": { "message": "Glavna lozinka" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Dodijeli zadatke" }, + "assignTasksToMembers": { + "message": "Dodijeli zadatke članovima za vođeno rješavanje" + }, "assignToCollections": { "message": "Dodijeli zbirkama" }, @@ -12016,12 +12068,12 @@ "message": "Argon2id nudi jaču zaštitu od modernih napada. Najbolje za napredne korisnike s moćnim uređajima." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Poštanski broj" }, "cardNumberLabel": { - "message": "Card number" + "message": "Broj kartice" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Započni besplatno probno razdoblje za Families" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 0753f1d77c7..0337a402522 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Elérés intelligencia" }, - "riskInsights": { - "message": "Kockázati betekintés" - }, "passwordRisk": { "message": "Jelszó kockázat" }, + "noEditPermissions": { + "message": "Nincs jogosulltság ezen elem szerkesztéséhez." + }, "reviewAtRiskPasswords": { "message": "Tekintsük meg a veszélyeztetett jelszavakat (gyenge, nyilvános vagy újrafelhasznált) az alkalmazásokban. Válasszuk ki a legkritikusabb alkalmazásokat, hogy előnyben részesítsük a biztonsági műveleteket a felhasználók számára a veszélyeztetett jelszavak kezeléséhez." }, + "reviewAtRiskLoginsPrompt": { + "message": "Kockázatos bejelentkezések áttekintése" + }, "dataLastUpdated": { "message": "Az adatok utolsó frissítése: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "A kritikus alkalmazások megjelölésre kerültek." + }, "countOfCriticalApplications": { "message": "$COUNT$ kritikus alkalmazás", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Nem találhatók alkalmazások: $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "A szervezet bejelentkezési adatainak importálása a hitelesítő adatok biztonsági kockázat figyelésének megkezdéséhez. Az importálás után a következőkhöz jutunk el:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Kockázatok rangsorolása" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Összpontosítás a legfontosabb alkalmazásokra" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Kármentési útmutató" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Irányított feladatok hozzárendelése a veszélyeztetett tagokhoz a veszélyeztetett jogosultságok rotálásához." }, "benefit3Title": { - "message": "Monitor progress" + "message": "Folyamatok nyomonkövetése" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Kövessük az idő múlásával bekövetkező változásokat a biztonsági fejlesztések megjelenítéséhez." }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Futtassuk le az első jelentést az alkalmazások megtekintéséhez" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Készítsünk kockázati betekintés jelentést a szervezet alkalmazásainak elemzéséhez és azonosítsuk azokat a veszélyeztetett jelszavakat, amelyekre figyelmet kell fordítani. Az első jelentés futtatása:" }, "noCriticalApplicationsTitle": { "message": "Egyetlen alkalmazás sem lett megjelölve kritikusként." @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Kritikusként megjelölt alkalmazások" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ alkalmazás kritikusként lett megjelölve.", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Nem sikerült kritikusként megjelölni a kérelmeket." }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Kockázatos jelszavú tagok" + }, + "membersWillReceiveNotification": { + "message": "A tagok értesítést kapnak a kockázatos bejelentkezések megoldásáról a böngésző bővítményen keresztül." + }, "membersAtRiskCount": { "message": "$COUNT$ kockázatos tag", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Áttekintés most" }, + "allCaughtUp": { + "message": "Minden rendben!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Jelenleg nincs új falkalmazás felülvizsgálatra." + }, "prioritizeCriticalApplications": { "message": "Kritikus alkalmazások rangsorolása" }, - "atRiskItems": { - "message": "Kockázatos elemek" + "selectCriticalApplicationsDescription": { + "message": "Válasszuk ki, hogy mely alkalmazások a legkritikusabbak a szervezet számára, majd rendeljünk biztonsági feladatokat a tagokhoz a kockázatok megoldása érdekében." + }, + "reviewNewApplications": { + "message": "Új alkalmazások felülvizsgálata" + }, + "reviewNewApplicationsDescription": { + "message": "Kiemelésre került az Admin konzolban tárolt új alkalmazások veszélyeztetett elemei, amelyek gyenge, kitett vagy újrafelhasznált jelszavakkal rendelkeznek." + }, + "clickIconToMarkAppAsCritical": { + "message": "Kattintsunk a csillag ikonra egy alkalmazás kritikusként megjelöléséhez." }, "markAsCriticalPlaceholder": { "message": "A kritikus funkcióként megjelölés egy jövőbeli frissítésben lesz megvalósítva." }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Az alkalmazás átvizsgálása mentésre került." }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Úh alkalmazások lettek átvizsgálva." }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Hiba történt az átvizsgálási állapot mentésekor." }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Próbáljuk újra" }, "unmarkAsCritical": { "message": "Ktritkus jelölés eltávolítása" @@ -835,6 +862,9 @@ "favorites": { "message": "Kedvencek" }, + "taskSummary": { + "message": "Feladat összefoglaló" + }, "types": { "message": "Típusok" }, @@ -1360,7 +1390,7 @@ "message": "Egyszeri bejelentkezés használata" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A szervezet egyszeri bejelentkezést igényel." }, "welcomeBack": { "message": "Üdvözlet újra" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Böngésző frissítése" }, - "generatingYourRiskInsights": { - "message": "A kockázati betekintések generálása..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Jelentés futtatása" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Az önálló szervezet irányelv nem engedélyezett. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Bárkinek, aki egynél több szervezet tagja, visszavonják a hozzáférését, amíg nem hagyja el a többi szervezetet." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Minden tagnak csak ehhez a szervezethez kell tartoznia az automatizálás aktiválásához." }, "autoConfirmSingleOrgExemption": { "message": "Az egységes szervezeti irányelv minden szerepkörre kiterjed. " @@ -5872,6 +5902,19 @@ "message": "Ne engedjük, hogy a felhasználók elrejtsék email címüket a címzettek elől a Send elem létrehozása vagy szerkesztése során.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Alapértelmezett URI egyezés érzékelés" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Határozzuk meg, hogy mikor javasolt a bejelentkezés az automatikus kitöltéshez. Az adminisztrátorok és tulajdonosok mentesülnek e szabály alól." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Alapértelmezett URI egyezés érzékelés" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Válasszunk egy érvényes URI egyezés észlelési lehetőséget.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ szabály módosításra került.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "A mesterjelszó nem felel meg egy vagy több szervezeti szabályzatnak. A széf eléréséhez frissíteni kell a meszerjelszót. A továbblépés kijelentkeztet az aktuális munkamenetből és újra be kell jelentkezni. A többi eszközön lévő aktív munkamenetek akár egy óráig is aktívak maradhatnak." }, - "automaticAppLogin": { - "message": "A felhasználók automatikus bejelentkezése az engedélyezett alkalmazásokhoz" + "automaticAppLoginWithSSO": { + "message": "Automatikus bejelentkezés az SSO-val" }, - "automaticAppLoginDesc": { - "message": "A bejelentkezési űrlapok automatikusan kitöltésre és elküldésre kerülnek a konfigurált azonosság szolgáltatótól indított alkalmazásokhoz." + "automaticAppLoginWithSSODesc": { + "message": "Az SSO biztonságának és kényelmének kiterjesztése a nem-felügyelt alkalmazásokra. Amikor a felhasználók elindítanak egy alkalmazást a saját azonosítás szolgáltatónktól, a bejelentkezési adatokk automatikusan kitöltésre és elküldésre kerülnek, így egy kattintással biztonságos áramlás jön létre az azonosítás szolgáltatótól az alkalmazás felé." }, "automaticAppLoginIdpHostLabel": { "message": "Azonosság szolgáltató kiszolgáló" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." }, + "selfHostedEnvMustUseHttps": { + "message": "A webcímeknek HTTPS-t kell használniuk." + }, "apiUrl": { "message": "API szerver webcím" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "A hozzáférés megtagadásra került. Nincs jogosultság az oldal megtekintésére." }, + "noPageAccess": { + "message": "Nincs hozzáférés ehhez az oldalhoz." + }, "masterPassword": { "message": "Mesterjelszó" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Feladatok hozzárendelése" }, + "assignTasksToMembers": { + "message": "Feladatok hozzárendelése a tagokhoz irányított megoldáshoz." + }, "assignToCollections": { "message": "Hozzárendelés gyűjteményekhez" }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 2dfdfceec11..0cab95e4de5 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -17,17 +17,20 @@ "accessIntelligence": { "message": "Akses Pintar" }, - "riskInsights": { - "message": "Wawasan Risiko" - }, "passwordRisk": { "message": "Petunjuk Sandi" }, + "noEditPermissions": { + "message": "Anda tidak memiliki izin untuk mengubah item ini" + }, "reviewAtRiskPasswords": { "message": "Tinjau sandi berisiko (lemah, bocor, atau digunakan ulang) lintas aplikasi. Pilih aplikasi paling genting Anda untuk mengutamakan aksi keamanan untuk pengguna Anda guna menangani sandi berisiko." }, + "reviewAtRiskLoginsPrompt": { + "message": "Tinjau login yang berisiko" + }, "dataLastUpdated": { - "message": "Data terakhir diperbarui pada: $DATE$", + "message": "Data terakhir diperbarui: $DATE$", "placeholders": { "date": { "content": "$1", @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "aplikasi penting ditandai" + }, "countOfCriticalApplications": { "message": "$COUNT$ aplikasi penting", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Tidak ada aplikasi ditemukan untuk $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,13 +188,13 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Impor data login organisasi Anda untuk mulai memantau risiko keamanan kredensial. Setelah diimpor, Anda akan mendapatkan:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Prioritaskan risiko" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Fokus pada aplikasi yang paling penting" }, "benefit2Title": { "message": "Guide remediation" @@ -197,13 +203,13 @@ "message": "Assign at-risk members guided tasks to rotate at-risk credentials" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Memantau kemajuan" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Lacak perubahan dari waktu ke waktu untuk menunjukkan peningkatan keamanan" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Jalankan laporan pertama Anda untuk melihat aplikasi" }, "noReportRunDescription": { "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplikasi ditandai sebagai penting" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Gagal menandai aplikasi sebagai penting" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Anggota dengan akses ke item berisiko untuk apl penting" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ anggota berisiko", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Tinjau sekarang" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritaskan aplikasi penting" }, - "atRiskItems": { - "message": "Item berisiko" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Fungsi tandai sebagai penting akan diterapkan pada pembaruan mendatang" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -380,7 +407,7 @@ "message": "Berhasil membatalkan apl sebagai penting" }, "whatTypeOfItem": { - "message": "Jenis barang apa ini?" + "message": "Jenis item apa ini?" }, "name": { "message": "Nama" @@ -423,7 +450,7 @@ "message": "Catatan" }, "customFields": { - "message": "Kolom Ubahsuai" + "message": "Bidang ubahsuai" }, "cardholderName": { "message": "Nama Pemegang Kartu" @@ -453,7 +480,7 @@ } }, "itemHistory": { - "message": "Riwayat barang" + "message": "Riwayat item" }, "authenticatorKey": { "message": "Kunci Autentikator" @@ -521,7 +548,7 @@ "message": "Merek" }, "expiration": { - "message": "Masa Berlaku" + "message": "Masa berlaku" }, "securityCode": { "message": "Kode Keamanan (CVV)" @@ -680,7 +707,7 @@ "message": "Edit Folder" }, "editWithName": { - "message": "Sunting $ITEM$: $NAME$", + "message": "Ubah $ITEM$: $NAME$", "placeholders": { "item": { "content": "$1", @@ -763,7 +790,7 @@ "message": "Simpan" }, "cancel": { - "message": "Batalkan" + "message": "Batal" }, "later": { "message": "Nanti" @@ -784,7 +811,7 @@ "message": "Batalkan favorit" }, "edit": { - "message": "Sunting" + "message": "Ubah" }, "searchCollection": { "message": "Cari koleksi" @@ -835,6 +862,9 @@ "favorites": { "message": "Favorit" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Jenis" }, @@ -881,7 +911,7 @@ "message": "Nama Tengah" }, "lastName": { - "message": "Nama Belakang" + "message": "Nama belakang" }, "fullName": { "message": "Nama Lengkap" @@ -905,7 +935,7 @@ "message": "Negara Bagian / Provinsi" }, "zipPostalCode": { - "message": "Kode Pos" + "message": "Zip / Kode pos" }, "country": { "message": "Negara" @@ -920,7 +950,7 @@ "message": "Pilih" }, "newItem": { - "message": "Barang baru" + "message": "Item baru" }, "addItem": { "message": "Tambah Item" @@ -1012,13 +1042,13 @@ "description": "for adding new items" }, "item": { - "message": "Barang" + "message": "Item" }, "itemDetails": { - "message": "Rincian barang" + "message": "Rincian item" }, "itemName": { - "message": "Nama barang" + "message": "Nama item" }, "ex": { "message": "cth.", @@ -1062,15 +1092,15 @@ "message": "Sandi disalin" }, "copyUsername": { - "message": "Salin Nama Pengguna", + "message": "Salin nama pengguna", "description": "Copy username to clipboard" }, "copyNumber": { - "message": "Salin Nomor", + "message": "Salin nomor", "description": "Copy credit card number" }, "copySecurityCode": { - "message": "Salin Kode Keamanan", + "message": "Salin kode keamanan", "description": "Copy credit card security code (CVV)" }, "copyUri": { @@ -1146,7 +1176,7 @@ "message": "Saya" }, "myItems": { - "message": "Barang saya" + "message": "Item saya" }, "myVault": { "message": "Brankas Saya" @@ -1222,7 +1252,7 @@ } }, "itemsMovedToOrg": { - "message": "Barang dipindahkan ke $ORGNAME$", + "message": "Item dipindahkan ke $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -1231,7 +1261,7 @@ } }, "itemMovedToOrg": { - "message": "Barang dipindahkan ke $ORGNAME$", + "message": "Item dipindahkan ke $ORGNAME$", "placeholders": { "orgname": { "content": "$1", @@ -1895,7 +1925,7 @@ "message": "Kode Verifikasi (TOTP)" }, "copyVerificationCode": { - "message": "Salin Kode Verifikasi" + "message": "Salin kode verifikasi" }, "copyUuid": { "message": "Salin UUID" @@ -2178,10 +2208,10 @@ "message": "Zona bahaya" }, "deauthorizeSessions": { - "message": "Batalkan Otorisasi Sesi" + "message": "Cabut otorisasi sesi" }, "deauthorizeSessionsDesc": { - "message": "Khawatir akun Anda masuk di perangkat lain? Lanjutkan di bawah untuk membatalkan otorisasi semua komputer atau perangkat yang pernah Anda gunakan sebelumnya. Langkah keamanan ini disarankan jika Anda sebelumnya menggunakan komputer publik atau secara tidak sengaja menyimpan sandi Anda di perangkat yang bukan milik Anda. Langkah ini juga akan menghapus semua sesi login dua langkah yang diingat sebelumnya." + "message": "Khawatir akun Anda masih aktif di perangkat lain? Lanjutkan cabut otorisasi semua perangkat yang pernah digunakan. Ini disarankan jika Anda memakai komputer umum atau menyimpan sandi di perangkat orang lain. Semua sesi login dua langkah juga akan dihapus." }, "deauthorizeSessionsWarning": { "message": "Melanjutkan juga akan mengeluarkan Anda dari sesi saat ini, mengharuskan Anda untuk masuk kembali. Anda juga akan diminta untuk masuk dua langkah lagi, jika diaktifkan. Sesi aktif di perangkat lain dapat terus aktif hingga satu jam." @@ -2220,7 +2250,7 @@ } }, "purgeVault": { - "message": "Kosongkan Brankas" + "message": "Hapus brankas" }, "purgedOrganizationVault": { "message": "Kubah organisasi yang dibersihkan." @@ -2229,16 +2259,16 @@ "message": "Brankas diakses oleh provider." }, "purgeVaultDesc": { - "message": "Lanjutkan di bawah untuk menghapus semua item dan folder di lemari besi Anda. Item milik organisasi yang Anda bagikan tidak akan dihapus." + "message": "Lanjutkan di bawah ini untuk menghapus semua item dan folder di brankas Anda. Item milik organisasi yang Anda bagikan tidak akan dihapus." }, "purgeOrgVaultDesc": { - "message": "Lanjutkan di bawah untuk menghapus semua item di lemari besi organisasi." + "message": "Lanjutkan di bawah ini untuk menghapus semua item di brankas organisasi." }, "purgeVaultWarning": { - "message": "Membersihkan lemari besi Anda bersifat permanen. Itu tidak bisa dibatalkan." + "message": "Menghapus brankas Anda bersifat permanen. Tindakan ini tidak dapat dibatalkan." }, "vaultPurged": { - "message": "Lemari besi Anda telah dibersihkan." + "message": "Brankas dibersihkan." }, "deleteAccount": { "message": "Hapus akun" @@ -2294,7 +2324,7 @@ "message": "Ada masalah dengan data yang Anda coba impor. Harap selesaikan kesalahan yang tercantum di bawah ini di file sumber Anda dan coba lagi." }, "importSuccess": { - "message": "Data telah berhasil diimpor ke lemari besi Anda." + "message": "Data berhasil diimpor" }, "importSuccessNumberOfItems": { "message": "Sebanyak $AMOUNT$ item diimpor.", @@ -2360,7 +2390,7 @@ "message": "Tidak ada berkas yang dipilih" }, "orCopyPasteFileContents": { - "message": "atau salin / tempel konten file impor" + "message": "atau salin/tempel konten berkas impor" }, "instructionsFor": { "message": "$NAME$ Instruksi", @@ -2403,10 +2433,10 @@ "message": "Jika Anda memiliki info masuk yang sama di beberapa domain situs web yang berbeda, Anda dapat menandai situs web tersebut sebagai \"setara\". Domain \"Global\" sudah dibuat untuk Anda oleh Bitwarden." }, "globalEqDomains": { - "message": "Domain Setara Global" + "message": "Domain setara global" }, "customEqDomains": { - "message": "Domain Setara Kustom" + "message": "Domain setara khusus" }, "exclude": { "message": "Mengecualikan" @@ -2418,7 +2448,7 @@ "message": "Sesuaikan" }, "newCustomDomain": { - "message": "Domain Kustom Baru" + "message": "Domain khusus baru" }, "newCustomDomainDesc": { "message": "Masukkan daftar domain yang dipisahkan dengan koma. Hanya domain \"dasar\" yang diperbolehkan. Jangan masukkan subdomain. Misalnya, masukkan \"google.com\", bukan \"www.google.com\". Anda juga dapat memasukkan \"androidapp: //package.name\" untuk mengaitkan aplikasi android dengan domain situs lain." @@ -2476,7 +2506,7 @@ "message": "A policy set by 1 or more organizations prevents you from importing cards to your vaults." }, "yourSingleUseRecoveryCode": { - "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place." + "message": "Kode pemulihan sekali pakai ini bisa digunakan untuk menonaktifkan login dua langkah jika Anda kehilangan akses ke penyedia login dua langkah. Bitwarden menyarankan Anda mencatat kode ini dan menyimpannya dengan aman." }, "viewRecoveryCode": { "message": "Lihat Kode Pemulihan" @@ -2502,7 +2532,7 @@ "message": "Keanggotaan Premium" }, "premiumRequired": { - "message": "Memerlukan Keanggotaan Premium" + "message": "Tingkatkan premium" }, "premiumRequiredDesc": { "message": "Keanggotaan premium diperlukan untuk menggunakan fitur ini." @@ -2556,13 +2586,13 @@ "message": "," }, "twoStepAuthenticatorInstructionInfix2": { - "message": "or" + "message": "atau" }, "twoStepAuthenticatorInstructionSuffix": { "message": "." }, "continueToExternalUrlTitle": { - "message": "Continue to $URL$?", + "message": "Lanjut ke $URL$?", "placeholders": { "url": { "content": "$1", @@ -2577,19 +2607,19 @@ "message": "Continue to bitwarden.com?" }, "twoStepContinueToBitwardenUrlDesc": { - "message": "Bitwarden Authenticator allows you to store authenticator keys and generate TOTP codes for 2-step verification flows. Learn more on the bitwarden.com website." + "message": "Bitwarden Authenticator memungkinkan Anda menyimpan kunci autentikator dan menghasilkan kode TOTP untuk verifikasi 2-langkah. Pelajari lebih lanjut di situs web bitwarden.com." }, "twoStepAuthenticatorScanCodeV2": { - "message": "Scan the QR code below with your authenticator app or enter the key." + "message": "Pindai kode QR di bawah ini dengan apl autentikator Anda atau masukkan kuncinya." }, "twoStepAuthenticatorQRCanvasError": { - "message": "Could not load QR code. Try again or use the key below." + "message": "Tidak dapat memuat kode QR. Coba lagi atau gunakan kunci di bawah ini." }, "key": { "message": "Kunci" }, "twoStepAuthenticatorEnterCodeV2": { - "message": "Verification code" + "message": "Kode verifikasi" }, "twoStepAuthenticatorReaddDesc": { "message": "Jika Anda perlu menambahkannya ke perangkat lain, di bawah ini adalah kode QR (atau kunci) yang diperlukan oleh aplikasi autentikator Anda." @@ -2670,7 +2700,7 @@ "message": "Masukkan informasi aplikasi Bitwarden dari panel Duo Admin Anda." }, "twoFactorDuoClientId": { - "message": "Client Id" + "message": "Id Klien" }, "twoFactorDuoClientSecret": { "message": "Rahasia Klien" @@ -2746,7 +2776,7 @@ "message": "Laporan" }, "reportsDesc": { - "message": "Identifikasi dan tutup celah keamanan di akun-akun online Anda dengan meng-klik laporan di bawah ini.", + "message": "Identifikasi dan tutup celah keamanan di akun online Anda dengan memilih laporan di bawah ini.", "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "orgsReportsDesc": { @@ -2754,7 +2784,7 @@ "description": "Vault health reports can be used to evaluate the security of your Bitwarden individual or organization vault." }, "unsecuredWebsitesReport": { - "message": "Laporan Situs Web Tidak Aman" + "message": "Situs Web Tidak Aman" }, "unsecuredWebsitesReportDesc": { "message": "Menggunakan situs web tidak aman dengan skema http: // dapat berbahaya. Jika situs web memungkinkan, Anda harus selalu mengaksesnya menggunakan skema https: // sehingga koneksi Anda terenkripsi." @@ -2779,7 +2809,7 @@ "message": "Tidak ada item di vault Anda yang memiliki URI tidak aman." }, "inactive2faReport": { - "message": "Laporan 2FA tidak aktif" + "message": "Login Dua-Langkah Nonaktif" }, "inactive2faReportDesc": { "message": "Otentikasi dua faktor (2FA) adalah pengaturan keamanan penting yang membantu mengamankan akun Anda. Jika situs web menawarkannya, Anda harus selalu mengaktifkan otentikasi dua faktor." @@ -2801,13 +2831,13 @@ } }, "noInactive2fa": { - "message": "Tidak ada situs web yang ditemukan di lemari besi Anda dengan konfigurasi otentikasi dua faktor yang hilang." + "message": "Tidak ada situs web yang ditemukan di brankas Anda dengan konfigurasi login dua langkah yang hilang." }, "instructions": { "message": "Instruksi" }, "exposedPasswordsReport": { - "message": "Sandi yang terekspos" + "message": "Sandi Terekspos" }, "exposedPasswordsReportDesc": { "message": "Sandi yang terekspos saat terjadi pelanggaran data merupakan target mudah bagi penyerang. Ubah sandi ini untuk mencegah potensi pembobolan." @@ -2847,7 +2877,7 @@ } }, "weakPasswordsReport": { - "message": "Sandi lemah" + "message": "Sandi Lemah" }, "weakPasswordsReportDesc": { "message": "Sandi yang lemah dapat dengan mudah ditebak oleh penyerang. Ubah sandi tersebut menjadi sandi yang kuat menggunakan generator sandi." @@ -2875,7 +2905,7 @@ "message": "Kelemahan" }, "reusedPasswordsReport": { - "message": "Sandi yang digunakan berulang" + "message": "Sandi Digunakan Berulang" }, "reusedPasswordsReportDesc": { "message": "Menggunakan sandi berulang akan memudahkan penyerang membobol banyak akun. Ubah sandi ini agar masing-masing unik." @@ -2912,7 +2942,7 @@ } }, "dataBreachReport": { - "message": "Laporan Pelanggaran Data" + "message": "Pelanggaran Data" }, "breachDesc": { "message": "\"Pelanggaran\" adalah insiden ketika data situs telah diakses secara ilegal oleh peretas dan kemudian dirilis ke publik. Tinjau jenis data yang disusupi (alamat email, sandi, kartu kredit, dll.) Dan lakukan tindakan yang sesuai, seperti mengubah sandi." @@ -3005,7 +3035,7 @@ "message": "Anda tidak memiliki cukup kredit akun untuk pembelian ini. Anda dapat menambahkan kredit ke akun Anda dari halaman Penagihan." }, "creditAppliedDesc": { - "message": "Saldo akun Anda dapat digunakan untuk melakukan pembelian. Semua saldo yang tersedia akan secara otomatis ditagihkan ke faktur yang dibuat untuk akun ini." + "message": "Kredit akun Anda dapat digunakan untuk melakukan pembelian. Semua kredit yang tersedia akan secara otomatis diterapkan ke faktur yang dibuat untuk akun ini." }, "goPremium": { "message": "Jadi Anggota Premium", @@ -3030,7 +3060,7 @@ "message": "Kebersihan sandi, kesehatan akun, dan laporan pelanggaran data untuk menjaga brankas Anda tetap aman." }, "premiumSignUpTotp": { - "message": "Generator kode verifikasi TOTP (2FA) untuk login di lemari besi Anda." + "message": "Generator kode verifikasi TOTP (2FA) untuk login di brankas Anda." }, "premiumSignUpSupport": { "message": "Dukungan pelanggan prioritas." @@ -3164,7 +3194,7 @@ "message": "Klik tombol PayPal untuk masuk ke akun PayPal Anda, lalu klik tombol Kirim di bawah untuk melanjutkan." }, "cancelSubscription": { - "message": "Batalkan Langganan" + "message": "Batalkan langganan" }, "subscriptionExpiration": { "message": "Kedaluwarsa langganan" @@ -3350,7 +3380,7 @@ "message": "Alamat email akun Anda harus diverifikasi." }, "newOrganizationDesc": { - "message": "Organisasi memungkinkan Anda berbagi bagian lemari besi Anda dengan orang lain serta mengelola pengguna terkait untuk entitas tertentu seperti keluarga, tim kecil, atau perusahaan besar." + "message": "Organisasi memungkinkan Anda berbagi bagian brankas Anda dengan orang lain serta mengelola pengguna terkait untuk entitas tertentu seperti keluarga, tim kecil, atau perusahaan besar." }, "generalInformation": { "message": "Informasi Umum" @@ -3625,7 +3655,7 @@ "message": "Tambahkan Grup" }, "editGroup": { - "message": "Sunting Grup" + "message": "Ubah grup" }, "deleteGroupConfirmation": { "message": "Apakah Anda yakin ingin menghapus grup ini?" @@ -3649,7 +3679,7 @@ "message": "Ketika status keanggotaan dicabut, mereka tidak lagi memiliki akses ke data organisasi. Untuk mengembalikan akses anggota secara cepat, buka tab Dicabut." }, "removeUserConfirmationKeyConnector": { - "message": "Peringatan! Pengguna ini memerlukan Key Connector untuk mengelola enkripsi mereka. Menghapus pengguna ini dari organisasi Anda akan menonaktifkan akun mereka secara permanen. Tindakan ini tidak dapat dibatalkan. Apakah Anda ingin melanjutkan?" + "message": "Peringatan! Pengguna ini memerlukan Key Connector untuk mengelola enkripsinya. Menghapus pengguna ini dari organisasi Anda akan menonaktifkan akunnya secara permanen. Tindakan ini tidak dapat dibatalkan. Ingin melanjutkan?" }, "externalId": { "message": "Id Eksternal" @@ -3793,13 +3823,13 @@ "message": "Brankas web" }, "webApp": { - "message": "Web app" + "message": "Aplikasi web" }, "cli": { "message": "CLI" }, "bitWebVault": { - "message": "Bitwarden Web vault" + "message": "Brankas Web Bitwarden" }, "bitSecretsManager": { "message": "Manajer Rahasia Bitwarden" @@ -3826,13 +3856,13 @@ "message": "Upaya masuk gagal dengan proses masuk dua langkah yang salah." }, "incorrectPassword": { - "message": "Incorrect password" + "message": "Sandi salah" }, "incorrectCode": { - "message": "Incorrect code" + "message": "Kode salah" }, "incorrectPin": { - "message": "Incorrect PIN" + "message": "PIN salah" }, "pin": { "message": "PIN", @@ -3884,7 +3914,7 @@ } }, "viewAllLogInOptions": { - "message": "View all log in options" + "message": "Lihat semua pilihan masuk" }, "viewAllLoginOptions": { "message": "Lihat semua opsi log in" @@ -3935,7 +3965,7 @@ } }, "viewCollectionWithName": { - "message": "View collection - $NAME$", + "message": "Lihat koleksi - $NAME$", "placeholders": { "name": { "content": "$1", @@ -3944,7 +3974,7 @@ } }, "editItemWithName": { - "message": "Edit item - $NAME$", + "message": "Ubah item - $NAME$", "placeholders": { "name": { "content": "$1", @@ -4007,7 +4037,7 @@ } }, "deletedCollections": { - "message": "Deleted collections" + "message": "Koleksi yang dihapus" }, "deletedCollectionId": { "message": "Koleksi $ID$ dihapus.", @@ -4055,7 +4085,7 @@ } }, "deletedManyGroups": { - "message": "Deleted $QUANTITY$ group(s).", + "message": "Menghapus $QUANTITY$ grup.", "placeholders": { "quantity": { "content": "$1", @@ -4223,22 +4253,22 @@ "message": "Perangkat" }, "loginStatus": { - "message": "Login status" + "message": "Status login" }, "firstLogin": { - "message": "First login" + "message": "Login pertama" }, "trusted": { - "message": "Trusted" + "message": "Terpercaya" }, "needsApproval": { - "message": "Needs approval" + "message": "Perlu persetujuan" }, "areYouTryingtoLogin": { - "message": "Are you trying to log in?" + "message": "Apakah Anda mencoba masuk?" }, "logInAttemptBy": { - "message": "Login attempt by $EMAIL$", + "message": "Upaya masuk oleh $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -4247,22 +4277,22 @@ } }, "deviceType": { - "message": "Device Type" + "message": "Jenis Perangkat" }, "ipAddress": { - "message": "IP Address" + "message": "Alamat IP" }, "confirmLogIn": { - "message": "Confirm login" + "message": "Konfirmasi masuk" }, "denyLogIn": { - "message": "Deny login" + "message": "Tolak masuk" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "Permintaan ini tidak lagi valid." }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "Permintaan masuk disetujui untuk $EMAIL$ di $DEVICE$", "placeholders": { "email": { "content": "$1", @@ -4275,16 +4305,16 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "Anda menolak upaya masuk dari perangkat lain. Jika ini Anda, coba masuk lagi dengan perangkat tersebut." }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "Permintaan masuk telah kedaluwarsa." }, "justNow": { - "message": "Just now" + "message": "Baru saja" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "Diminta $MINUTES$ menit yang lalu", "placeholders": { "minutes": { "content": "$1", @@ -4293,25 +4323,25 @@ } }, "creatingAccountOn": { - "message": "Creating account on" + "message": "Membuat akun di" }, "checkYourEmail": { - "message": "Check your email" + "message": "Periksa email Anda" }, "followTheLinkInTheEmailSentTo": { - "message": "Follow the link in the email sent to" + "message": "Ikuti tautan di email yang dikirim ke" }, "andContinueCreatingYourAccount": { - "message": "and continue creating your account." + "message": "dan lanjutkan membuat akun Anda." }, "noEmail": { - "message": "No email?" + "message": "Tidak ada email?" }, "goBack": { - "message": "Go back" + "message": "Kembali" }, "toEditYourEmailAddress": { - "message": "to edit your email address." + "message": "untuk mengubah alamat email Anda." }, "view": { "message": "Tampilan" @@ -4395,7 +4425,7 @@ "message": "Email Anda telah diverifikasi." }, "emailVerifiedV2": { - "message": "Email verified" + "message": "Email terverifikasi" }, "emailVerifiedFailed": { "message": "Tidak dapat memverifikasi email Anda. Coba kirim email verifikasi baru." @@ -4409,17 +4439,17 @@ "updateBrowser": { "message": "Perbarui Browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { - "message": "Run report" + "message": "Jalankan laporan" }, "updateBrowserDesc": { "message": "Anda menggunakan browser web yang tidak didukung. Kubah web mungkin tidak berfungsi dengan baik." }, "youHaveAPendingLoginRequest": { - "message": "You have a pending login request from another device." + "message": "Anda memiliki permintaan masuk tertunda dari perangkat lain." }, "reviewLoginRequest": { "message": "Tinjau permintaan masuk" @@ -4498,7 +4528,7 @@ "message": "Undangan Diterima" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Berhasil menerima undangan Anda." }, "inviteInitAcceptedDesc": { "message": "Anda sekarang dapat mengakses organisasi ini." @@ -4613,10 +4643,10 @@ } }, "viewInvoice": { - "message": "Lihat Faktur" + "message": "Lihat faktur" }, "downloadInvoice": { - "message": "Mengunduh Faktur" + "message": "Unduh faktur" }, "verifyBankAccount": { "message": "Verifikasi Rekening Bank" @@ -4813,7 +4843,7 @@ } }, "editFieldLabel": { - "message": "Edit $LABEL$", + "message": "Ubah $LABEL$", "placeholders": { "label": { "content": "$1", @@ -4822,7 +4852,7 @@ } }, "reorderToggleButton": { - "message": "Reorder $LABEL$. Use arrow key to move item up or down.", + "message": "Atur ulang $LABEL$. Gunakan tombol panah untuk memindahkan item ke atas atau ke bawah.", "placeholders": { "label": { "content": "$1", @@ -4880,7 +4910,7 @@ "message": "Memuat" }, "upgrade": { - "message": "Meningkatkan" + "message": "Tingkatkan" }, "upgradeOrganization": { "message": "Tingkatkan Organisasi" @@ -4904,19 +4934,19 @@ "message": "Get advice, announcements, and research opportunities from Bitwarden in your inbox." }, "subscribe": { - "message": "Subscribe" + "message": "Langganan" }, "unsubscribe": { - "message": "Unsubscribe" + "message": "Berhenti langganan" }, "atAnyTime": { - "message": "at any time." + "message": "kapan pun." }, "byContinuingYouAgreeToThe": { - "message": "By continuing, you agree to the" + "message": "Dengan melanjutkan, Anda menyetujui" }, "and": { - "message": "and" + "message": "dan" }, "acceptPolicies": { "message": "Dengan mencentang kotak ini, anda menyetujui yang berikut:" @@ -4934,16 +4964,16 @@ "message": "Filter" }, "vaultTimeout": { - "message": "Batas Waktu Brankas" + "message": "Batas waktu brankas" }, "vaultTimeout1": { - "message": "Timeout" + "message": "Batas waktu" }, "vaultTimeoutDesc": { "message": "Pilih batas waktu untuk brankas Anda mengambil tindakan yang dipilih." }, "vaultTimeoutLogoutDesc": { - "message": "Choose when your vault will be logged out." + "message": "Pilih kapan brankas Anda akan keluar." }, "oneMinute": { "message": "1 menit" @@ -4982,13 +5012,13 @@ "message": "Organisasi dinonaktifkan." }, "organizationIsSuspended": { - "message": "Organization is suspended" + "message": "Organisasi ditangguhkan" }, "organizationIsSuspendedDesc": { - "message": "Items in suspended organizations cannot be accessed. Contact your organization owner for assistance." + "message": "Item dalam organisasi yang ditangguhkan tidak dapat diakses. Hubungi pemilik organisasi Anda untuk mendapatkan bantuan." }, "secretsAccessSuspended": { - "message": "Suspended organizations cannot be accessed. Please contact your organization owner for assistance." + "message": "Organisasi yang ditangguhkan tidak dapat diakses. Silakan hubungi pemilik organisasi Anda untuk mendapatkan bantuan." }, "secretsCannotCreate": { "message": "Rahasia tidak dapat dibuat dalam organisasi yang ditangguhkan. Silakan hubungi pemilik organisasi Anda untuk mendapatkan bantuan." @@ -5059,7 +5089,7 @@ "description": "This is a verb. ex. 'Fix The Car'" }, "oldAttachmentsNeedFixDesc": { - "message": "Ada lampiran file lama di lemari besi Anda yang perlu diperbaiki sebelum Anda dapat merotasi kunci enkripsi akun Anda." + "message": "Ada lampiran berkas lama di brankas Anda yang perlu diperbaiki sebelum Anda dapat memutar kunci enkripsi akun Anda." }, "yourAccountsFingerprint": { "message": "Frase sidik jari akun Anda", @@ -5124,7 +5154,7 @@ "message": "Panjang minimum" }, "clone": { - "message": "Klon" + "message": "Duplikat" }, "masterPassPolicyTitle": { "message": "Persyaratan sandi utama" @@ -5151,7 +5181,7 @@ "message": "Anggota organisasi yang tidak mengaktifkan login dua langkah untuk akun pribadinya akan dihapus dari organisasi dan akan menerima email yang memberi tahu mereka tentang perubahan tersebut." }, "twoStepLoginPolicyUserWarning": { - "message": "Anda adalah anggota organisasi yang memerlukan login dua langkah untuk diaktifkan di akun pengguna Anda. Jika Anda menonaktifkan semua penyedia proses masuk dua langkah, Anda akan secara otomatis dihapus dari organisasi ini." + "message": "Anda adalah anggota organisasi yang mewajibkan pengaturan login dua langkah pada akun pengguna Anda. Jika Anda menonaktifkan semua penyedia login dua langkah, Anda akan otomatis dihapus dari organisasi tersebut." }, "passwordGeneratorPolicyDesc": { "message": "Tetapkan persyaratan untuk pembuat sandi." @@ -5202,20 +5232,20 @@ "message": "Jumlah kata minimum" }, "overridePasswordTypePolicy": { - "message": "Password Type", + "message": "Jenis Sandi", "description": "Name of the password generator policy that overrides the user's password/passphrase selection." }, "userPreference": { "message": "Preferensi Pengguna" }, "vaultTimeoutAction": { - "message": "Tindakan ketika Batas Waktu Brankas Habis" + "message": "Tindakan batas waktu brankas" }, "vaultTimeoutActionLockDesc": { "message": "Sandi utama atau metode pembukaan kunci lainnya diperlukan untuk mengakses brankas Anda lagi." }, "vaultTimeoutActionLogOutDesc": { - "message": "Vault keluar mengharuskan Anda mengautentikasi ulang untuk mengaksesnya lagi." + "message": "Autentikasi ulang diperlukan untuk mengakses brankas Anda lagi." }, "lock": { "message": "Mengunci", @@ -5286,10 +5316,10 @@ } }, "vaultTimeoutLogOutConfirmation": { - "message": "Keluar akan menghapus semua akses ke lemari besi Anda dan memerlukan otentikasi online setelah periode batas waktu. Anda yakin ingin menggunakan pengaturan ini?" + "message": "Keluar akan menghapus semua akses ke brankas Anda dan memerlukan autentikasi online setelah batas waktu habis. Anda yakin ingin menggunakan pengaturan ini?" }, "vaultTimeoutLogOutConfirmationTitle": { - "message": "Konfirmasi Tindakan Timeout" + "message": "Konfirmasi tindakan batas waktu" }, "hidePasswords": { "message": "Sembunyikan sandi" @@ -5560,7 +5590,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "downloadAttachments": { - "message": "Download attachments" + "message": "Unduh lampiran" }, "sendAccessPasswordTitle": { "message": "Masukkan sandi untuk melihat Kirim ini", @@ -5622,7 +5652,7 @@ "message": "Akses Darurat Disetujui" }, "viewDesc": { - "message": "Dapat melihat semua item di lemari besi Anda sendiri." + "message": "Dapat melihat semua item dalam brankas Anda sendiri." }, "takeover": { "message": "Pengambilalihan" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Diperlukan kebijakan organisasi tunggal. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Siapa pun yang menjadi bagian dari lebih dari satu organisasi akan dicabut aksesnya hingga mereka meninggalkan organisasi lain." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Kebijakan organisasi tunggal akan diperluas ke semua peran. " @@ -5830,7 +5860,7 @@ "message": "Pemilik dan Administrator Organisasi dibebaskan dari penegakan kebijakan ini." }, "personalOwnershipSubmitError": { - "message": "Karena Kebijakan Perusahaan, Anda dilarang menyimpan item ke lemari besi pribadi Anda. Ubah opsi Kepemilikan ke organisasi dan pilih dari Koleksi yang tersedia." + "message": "Karena kebijakan Enterprise, Anda tidak dapat menyimpan item ke brankas individual Anda. Ubah opsi kepemilikan menjadi organisasi dan pilih dari koleksi yang tersedia." }, "desktopAutotypePolicy": { "message": "Desktop Autotype Default Setting" @@ -5872,6 +5902,19 @@ "message": "Pengguna tidak boleh menyembunyikan alamat email dari penerima ketika membuat atau mengubah Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Kebijakan yang diubah $ID$.", "placeholders": { @@ -5888,10 +5931,10 @@ "message": "Cukai taksiran" }, "custom": { - "message": "Adat" + "message": "Khusus" }, "customDesc": { - "message": "Memungkinkan kontrol yang lebih terperinci atas izin pengguna untuk konfigurasi lanjutan." + "message": "Berikan izin khusus kepada anggota" }, "customDescNonEnterpriseStart": { "message": "Custom roles is an ", @@ -5976,7 +6019,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineProductDesc": { - "message": "Bitwarden mengirim mentransmisikan informasi sementara yang sensitif kepada orang lain dengan mudah dan aman.", + "message": "Bitwarden Kirim mengirimkan informasi sensitif dan sementara kepada orang lain dengan mudah dan aman.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendAccessTaglineLearnMore": { @@ -6066,7 +6109,7 @@ "message": "Open your organization's" }, "usingTheMenuSelect": { - "message": "Using the menu, select" + "message": "Menggunakan menu, pilih" }, "toGrantAccessToSelectedMembers": { "message": "to grant access to selected members." @@ -6273,7 +6316,7 @@ "message": "Items that have been in trash more than 30 days will be automatically deleted." }, "trashCleanupWarningSelfHosted": { - "message": "Items that have been in trash for a while will be automatically deleted." + "message": "Item yang telah lama berada di tempat sampah akan dihapus secara otomatis." }, "passwordPrompt": { "message": "Pengingat ulang sandi utama" @@ -6294,7 +6337,7 @@ "message": "This action is not applicable to any of the selected users." }, "removeUsersWarning": { - "message": "Apakah Anda yakin ingin menghapus pengguna berikut? Proses ini mungkin memerlukan waktu beberapa detik untuk menyelesaikannya dan tidak dapat diinterupsi atau dibatalkan." + "message": "Yakin ingin menghapus pengguna berikut? Proses ini mungkin memerlukan waktu beberapa detik dan tidak dapat dihentikan atau dibatalkan." }, "removeOrgUsersConfirmation": { "message": "When member(s) are removed, they no longer have access to organization data and this action is irreversible. To add the member back to the organization, they must be invited and onboarded again. The process may take a few seconds to complete and cannot be interrupted or canceled." @@ -6523,13 +6566,13 @@ "message": "Your master password does not meet the policy requirements of this organization. In order to join the organization, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, "updateWeakMasterPasswordWarning": { - "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." + "message": "Sandi utama Anda tidak memenuhi satu atau lebih kebijakan organisasi. Untuk mengakses brankas, Anda harus memperbarui sandi utama sekarang. Melanjutkan akan mengeluarkan Anda dari sesi saat ini dan mengharuskan login ulang. Sesi aktif di perangkat lain mungkin tetap aktif hingga satu jam." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -6541,22 +6584,22 @@ "message": "Your organization has updated your decryption options. Please set a master password to access your vault." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Batas waktu sesi" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Setel batas waktu sesi maksimum untuk semua anggota, kecuali pemilik." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Batas waktu habis yang diizinkan" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Batas waktu habis yang diizinkan diperlukan." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Waktu tidak valid. Ubah setidaknya satu nilai." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Tindakan batas waktu sesi" }, "immediately": { "message": "Immediately" @@ -6565,7 +6608,7 @@ "message": "On system lock" }, "onAppRestart": { - "message": "On app restart" + "message": "Saat apl dimulai ulang" }, "hours": { "message": "Jam" @@ -6574,7 +6617,7 @@ "message": "Menit" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Anda yakin ingin memberikan batas waktu maksimum \"Tidak pernah\" untuk semua anggota?" }, "sessionTimeoutConfirmationNeverDescription": { "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." @@ -6659,7 +6702,7 @@ "message": "Ekspor Brankas Dinonaktifkan" }, "personalVaultExportPolicyInEffect": { - "message": "One or more organization policies prevents you from exporting your individual vault." + "message": "Satu atau beberapa kebijakan organisasi mencegah Anda mengekspor brankas individual Anda." }, "activateAutofill": { "message": "Activate auto-fill" @@ -6704,7 +6747,7 @@ "message": "Rahasia Klien" }, "metadataAddress": { - "message": "Alamat Metadata" + "message": "Alamat metadata" }, "oidcRedirectBehavior": { "message": "OIDC redirect behavior" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7245,16 +7291,16 @@ "message": "Pisahkan beberapa nilai dengan tanda koma." }, "sessionTimeout": { - "message": "Your session has timed out. Please go back and try logging in again." + "message": "Sesi Anda telah habis waktu. Silakan kembali dan coba masuk lagi." }, "exportingPersonalVaultTitle": { - "message": "Exporting individual vault" + "message": "Mengekspor brankas individual" }, "exportingOrganizationVaultTitle": { "message": "Exporting organization vault" }, "exportingIndividualVaultDescription": { - "message": "Only the individual vault items associated with $EMAIL$ will be exported. Organization vault items will not be included. Only vault item information will be exported and will not include associated attachments.", + "message": "Hanya item brankas individual yang terkait dengan $EMAIL$ yang akan diekspor. Item brankas organisasi tidak akan disertakan. Hanya informasi item brankas yang akan diekspor dan tidak akan menyertakan lampiran terkait.", "placeholders": { "email": { "content": "$1", @@ -7263,7 +7309,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included", + "message": "Hanya item brankas individual, termasuk lampiran yang terkait dengan $EMAIL$, yang akan diekspor. Item brankas organisasi tidak akan disertakan", "placeholders": { "email": { "content": "$1", @@ -7272,7 +7318,7 @@ } }, "exportingOrganizationVaultDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.", + "message": "Hanya brankas organisasi yang terkait dengan $ORGANIZATION$ yang akan diekspor. Item dalam brankas individual atau organisasi lain tidak akan disertakan.", "placeholders": { "organization": { "content": "$1", @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Akses ditolak. Anda tidak mempunyai izin untuk melihat halaman ini." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Sandi utama" }, @@ -7317,7 +7366,7 @@ "message": "Kembali ke Laporan" }, "organizationPicker": { - "message": "Organization picker" + "message": "Pemilih organisasi" }, "currentOrganization": { "message": "Current organization", @@ -7398,7 +7447,7 @@ } }, "plusAddressedEmail": { - "message": "Plus addressed email", + "message": "Alamat email plus", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { @@ -7489,7 +7538,7 @@ } }, "lastSync": { - "message": "Last sync", + "message": "Sinkron terakhir", "description": "Used as a prefix to indicate the last time a sync occurred. Example \"Last sync 1968-11-16 00:00:00\"" }, "sponsorshipsSynced": { @@ -7858,10 +7907,10 @@ "message": "Jumplah pengguna" }, "pickAnAvatarColor": { - "message": "Pick an avatar color" + "message": "Pilih warna avatar" }, "customizeAvatar": { - "message": "Customize avatar" + "message": "Sesuaikan avatar" }, "avatarUpdated": { "message": "Avatar updated" @@ -7897,19 +7946,19 @@ "message": "Custom Color" }, "selectPlaceholder": { - "message": "-- Select --" + "message": "-- Pilih --" }, "multiSelectPlaceholder": { "message": "-- Ketik untuk menyaring --" }, "multiSelectLoading": { - "message": "Retrieving options..." + "message": "Mengambil pilihan..." }, "multiSelectNotFound": { - "message": "No items found" + "message": "Tidak ada item ditemukan" }, "multiSelectClearAll": { - "message": "Clear all" + "message": "Bersihkan semua" }, "toggleCharacterCount": { "message": "Toggle character count", @@ -7927,7 +7976,7 @@ "description": "Description for the Projects field." }, "lastEdited": { - "message": "Last edited", + "message": "Terakhir diubah", "description": "The label for the date and time when a item was last edited." }, "editSecret": { @@ -8430,7 +8479,7 @@ "message": "Status" }, "lastChecked": { - "message": "Last checked" + "message": "Terakhir diperiksa" }, "editDomain": { "message": "Edit domain" @@ -8526,7 +8575,7 @@ "message": "Member role" }, "moreFromBitwarden": { - "message": "More from Bitwarden" + "message": "Lainnya dari Bitwarden" }, "switchProducts": { "message": "Switch products" @@ -8649,7 +8698,7 @@ "message": "Successfully uploaded license" }, "lastLicenseSync": { - "message": "Last license sync" + "message": "Sinkron lisensi terakhir" }, "billingSyncHelp": { "message": "Billing Sync help" @@ -8703,7 +8752,7 @@ "message": "Kelompok/Pengguna" }, "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": "Melanjutkan proses ini akan membuat Anda keluar dari semua sesi aktif. Anda perlu masuk kembali dan menyelesaikan login dua langkah jika ada. Kami menyarankan Anda mengekspor brankas sebelum mengubah pengaturan enkripsi untuk mencegah kehilangan data." }, "secretsManager": { "message": "Manajer Rahasia" @@ -8715,7 +8764,7 @@ "message": "This user can access Secrets Manager" }, "important": { - "message": "Important:" + "message": "Penting:" }, "viewAll": { "message": "View all" @@ -9023,7 +9072,7 @@ "message": "Remove access" }, "checkForBreaches": { - "message": "Check known data breaches for this password" + "message": "Periksa kebocoran data yang diketahui untuk sandi ini" }, "exposedMasterPassword": { "message": "Exposed Master Password" @@ -9120,7 +9169,7 @@ "message": "Trusted device encryption" }, "trustedDevices": { - "message": "Trusted devices" + "message": "Perangkat terpercaya" }, "memberDecryptionOptionTdeDescPart1": { "message": "Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The", @@ -9450,13 +9499,13 @@ "message": "Updated collection management setting" }, "passwordManagerPlanPrice": { - "message": "Password Manager plan price" + "message": "Harga paket Pengelola Sandi" }, "secretsManagerPlanPrice": { "message": "Secrets Manager plan price" }, "passwordManager": { - "message": "Password Manager" + "message": "Pengelola Sandi" }, "freeOrganization": { "message": "Free Organization" @@ -9648,7 +9697,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "Deteksi kecocokan URI mengatur cara Bitwarden mengenali saran isi otomatis.", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { @@ -9706,7 +9755,7 @@ "description": "Used as a form field label for a select input on the offboarding survey." }, "anyOtherFeedback": { - "message": "Is there any other feedback you'd like to share?", + "message": "Apakah ada masukan lain yang ingin Anda sampaikan?", "description": "Used as a form field label for a textarea input on the offboarding survey." }, "missingFeatures": { @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, @@ -9794,7 +9846,7 @@ } }, "addField": { - "message": "Add field" + "message": "Tambah bidang" }, "editField": { "message": "Edit field" @@ -10314,10 +10366,10 @@ "message": "Continue setting up your free trial of Bitwarden" }, "continueSettingUpPasswordManager": { - "message": "Continue setting up Bitwarden Password Manager" + "message": "Lanjutkan pengaturan Pengelola Sandi Bitwarden" }, "continueSettingUpFreeTrialPasswordManager": { - "message": "Continue setting up your free trial of Bitwarden Password Manager" + "message": "Lanjutkan pengaturan uji coba gratis Pengelola Sandi Bitwarden Anda" }, "continueSettingUpSecretsManager": { "message": "Continue setting up Bitwarden Secrets Manager" @@ -10345,7 +10397,7 @@ } }, "backTo": { - "message": "Back to $NAME$", + "message": "Kembali ke $NAME$", "description": "Navigate back to a previous folder or collection", "placeholders": { "name": { @@ -10355,7 +10407,7 @@ } }, "back": { - "message": "Back", + "message": "Kembali", "description": "Button text to navigate back" }, "removeItem": { @@ -10408,7 +10460,7 @@ "message": "Secure your infrastructure" }, "protectYourFamilyOrBusiness": { - "message": "Protect your family or business" + "message": "Lindungi keluarga atau bisnis Anda" }, "upgradeOrganizationCloseSecurityGaps": { "message": "Close security gaps with monitoring reports" @@ -10584,7 +10636,7 @@ "message": "After making updates in the Bitwarden cloud server, upload your license file to apply the most recent changes." }, "addToFolder": { - "message": "Add to folder" + "message": "Tambah ke folder" }, "selectFolder": { "message": "Select folder" @@ -10780,7 +10832,7 @@ "message": "Access to Premium features" }, "additionalStorageGbMessage": { - "message": "GB additional storage" + "message": "GB penyimpanan tambahan" }, "sshKeyAlgorithm": { "message": "Key algorithm" @@ -10861,7 +10913,7 @@ "message": "Your Secrets Manager subscription will upgrade based on the plan selected" }, "bitwardenPasswordManager": { - "message": "Bitwarden Password Manager" + "message": "Pengelola Sandi Bitwarden" }, "secretsManagerComplimentaryPasswordManager": { "message": "Your complimentary one year Password Manager subscription will upgrade to the selected plan. You will not be charged until the complimentary period is over." @@ -11017,13 +11069,13 @@ "message": "Contact your organization owner for assistance." }, "suspendedOwnerOrgMessage": { - "message": "To regain access to your organization, add a payment method." + "message": "Untuk kembali mengakses ke organisasi Anda, tambahkan metode pembayaran." }, "deleteMembers": { "message": "Delete members" }, "noSelectedMembersApplicable": { - "message": "This action is not applicable to any of the selected members." + "message": "Tindakan ini tidak berlaku untuk anggota mana pun yang dipilih." }, "deletedSuccessfully": { "message": "Deleted successfully" @@ -11056,13 +11108,13 @@ "message": "Remove members" }, "devices": { - "message": "Devices" + "message": "Perangkat" }, "deviceListDescription": { - "message": "Your account was logged in to each of the devices below. If you do not recognize a device, remove it now." + "message": "Akun Anda telah masuk di perangkat di bawah ini. Jika Anda tidak mengenali perangkat tersebut, hapus sekarang juga." }, "deviceListDescriptionTemp": { - "message": "Your account was logged in to each of the devices below." + "message": "Akun Anda masuk di perangkat di bawah ini." }, "claimedDomains": { "message": "Claimed domains" @@ -11147,7 +11199,7 @@ "message": "Domain claimed" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Item ditambahkan ke favorit" }, "itemRemovedFromFavorites": { "message": "Item removed from favorites" @@ -11162,7 +11214,7 @@ "message": "Key rotation successful" }, "rotationCompletedDesc": { - "message": "Your master password and encryption keys have been updated. Your other devices have been logged out." + "message": "Sandi utama dan kunci enkripsi Anda telah diperbarui. Perangkat Anda yang lain telah dikeluarkan." }, "trustUserEmergencyAccess": { "message": "Trust and confirm user" @@ -11337,7 +11389,7 @@ "message": "Existing organization" }, "selectOrganizationProviderPortal": { - "message": "Select an organization to add to your Provider Portal." + "message": "Pilih organisasi untuk ditambahkan ke Portal Penyedia Anda." }, "noOrganizations": { "message": "There are no organizations to list" @@ -11476,7 +11528,7 @@ "message": "New business unit" }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "Kirim informasi sensitif dengan aman", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { @@ -11648,7 +11700,7 @@ "message": "Your billing address has been updated." }, "paymentDetails": { - "message": "Payment details" + "message": "Detail pembayaran" }, "paymentMethodUpdated": { "message": "Your payment method has been updated." @@ -11657,7 +11709,7 @@ "message": "Your bank account has been verified." }, "availableCreditAppliedToInvoice": { - "message": "Any available credit will be automatically applied towards invoices generated for this account." + "message": "Setiap kredit yang tersedia akan secara otomatis diterapkan terhadap faktur yang dibuat untuk akun ini." }, "mustBePositiveNumber": { "message": "Must be a positive number" @@ -11666,7 +11718,7 @@ "message": "Card security code" }, "cardSecurityCodeDescription": { - "message": "Card security code, also known as CVV or CVC, is typically a 3 digit number printed on the back of your credit card or 4 digit number printed on the front above your card number." + "message": "Kode keamanan kartu, juga dikenal sebagai CVV atau CVC, biasanya berupa angka 3 digit yang tercetak di belakang kartu kredit Anda atau angka 4 digit yang tercetak di bagian depan di atas nomor kartu Anda." }, "verifyBankAccountWarning": { "message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the Payment Details page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended." @@ -11869,7 +11921,7 @@ "message": "Unlimited family collections" }, "familiesSharedStorage": { - "message": "Shared storage for important family info" + "message": "Penyimpanan bersama untuk info penting keluarga" }, "limitedUsersV2": { "message": "Up to $COUNT$ members", @@ -12013,7 +12065,7 @@ "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "Argon2id menawarkan perlindungan lebih kuat terhadap serangan modern. Bagus untuk pengguna tingkat lanjut dengan perangkat canggih." }, "zipPostalCodeLabel": { "message": "ZIP / Postal code" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 75492cb79cc..8a9eac612b4 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Intelligence sugli accessi" }, - "riskInsights": { - "message": "Approfondimento rischi" - }, "passwordRisk": { "message": "Rischio password" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Controlla le password a rischio (deboli, esposte o riutilizzate). Seleziona le applicazioni critiche per determinare la priorità delle azioni di sicurezza." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Ultimo aggiornamento dei dati: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ applicazioni critiche", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Nessuna applicazione trovata per $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,13 +188,13 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Importa i dati di accesso della tua organizzazione per iniziare a monitorare i rischi per la sicurezza delle credenziali. Una volta importati, potrai:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Priorità dei rischi" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Concentrati sulle applicazioni più importanti" }, "benefit2Title": { "message": "Guide remediation" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applicazioni contrassegnate come critiche" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Impossibile contrassegnare le applicazioni come critiche" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Membri con accesso agli elementi a rischio per applicazioni critiche" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ membri a rischio", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Controlla adesso" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Priorità alle applicazioni critiche" }, - "atRiskItems": { - "message": "Elementi a rischio" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "La funzionalità per contrassegnare gli elementi critici sarà implementata in un aggiornamento futuro" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Preferiti" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipi" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Aggiorna browser" }, - "generatingYourRiskInsights": { - "message": "Generazione delle tue informazioni sui rischi..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Avvia report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Mostra sempre l'indirizzo email del membro ai destinatari durante la creazione o la modifica di un Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Politica $ID$ modificata.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "La tua password principale non soddisfa uno o più politiche della tua organizzazione. Per accedere alla cassaforte, aggiornala ora. Procedere ti farà uscire dalla sessione corrente, richiedendoti di accedere di nuovo. Le sessioni attive su altri dispositivi potrebbero continuare a rimanere attive per un massimo di un'ora." }, - "automaticAppLogin": { - "message": "Accesso automatico degli utenti per le applicazioni consentite" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "I moduli di accesso verranno automaticamente compilati e inviati per le applicazioni lanciate dall'identity provider configurato." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Host Identity provider" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL del server API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Accesso negato. Non hai i permessi necessari per visualizzare questa pagina." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Password principale" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assegna attività" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assegna alle raccolte" }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 91bbf61dca7..6d56564c0c2 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "アクセス インテリジェンス" }, - "riskInsights": { - "message": "リスク分析" - }, "passwordRisk": { "message": "パスワードのリスク" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "危険なパスワード(強度が低い、流出済み、再利用)を、アプリをまたいで調査します。特に重要なアプリを選択して、危険なパスワードに優先対応するようユーザーに促しましょう。" }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "データの最終更新: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "お気に入り" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "タイプ" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "ブラウザを更新" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Send を作成または編集するとき、常にメンバーのメールアドレスを受信者と一緒に表示します。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "変更されたポリシー $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "マスターパスワードが組織のポリシーに適合していません。保管庫にアクセスするには、今すぐマスターパスワードを更新しなければなりません。続行すると現在のセッションからログアウトし、再度ログインする必要があります。 他のデバイス上のアクティブなセッションは、最大1時間アクティブであり続けることがあります。" }, - "automaticAppLogin": { - "message": "許可されたアプリでユーザーを自動的にログインする" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "ログインフォームは自動的に入力され、設定された ID プロバイダーから起動されたアプリに送信されます。" + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "ID プロバイダーホスト" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API サーバー URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "アクセスが拒否されました。このページを表示する権限がありません。" }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "マスターパスワード" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "コレクションに割り当てる" }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 2bdaea7f75d..43e46ef6d6b 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "რჩეულები" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "ტიპები" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 338f5c10d8a..e7051dee661 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 1680aaa057b..b18ca2a2cc4 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "ಮೆಚ್ಚುಗೆಗಳು" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "ರೀತಿಯ" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "ಬ್ರೌಸರ್ ನವೀಕರಿಸಿ" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "ಕಳುಹಿಸುವಿಕೆಯನ್ನು ರಚಿಸುವಾಗ ಅಥವಾ ಸಂಪಾದಿಸುವಾಗ ಬಳಕೆದಾರರು ತಮ್ಮ ಇಮೇಲ್ ವಿಳಾಸವನ್ನು ಸ್ವೀಕರಿಸುವವರಿಂದ ಮರೆಮಾಡಲು ಅನುಮತಿಸಬೇಡಿ.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "ಮಾರ್ಪಡಿಸಿದ ನೀತಿ $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 3faf6ce4f69..dbc9b219d05 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "즐겨찾기" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "유형" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "브라우저 업데이트" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "사용자가 Send를 생성하거나 수정할 때 받는 사람으로부터 자신의 이메일 주소를 숨기지 못하게 합니다.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ 정책을 편집했습니다.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index ee7c89f73ca..d85ffcb3784 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Piekļuves inteliģence" }, - "riskInsights": { - "message": "Risku ieskats" - }, "passwordRisk": { "message": "Paroļu risks" }, + "noEditPermissions": { + "message": "Nav nepieciešamo atļauju, lai labotu šo vienumu" + }, "reviewAtRiskPasswords": { "message": "Riskam pakļautu (vāju, atklātu vai vairākkārt izmantotu) paroļu pārskatīšana dažādās lietotnēs. Jāatlasa viskritiskākās paroles, lai noteiktu svarīgas drošības darbības, ko lietotājiem pielietot riskam pakļautām parolēm." }, + "reviewAtRiskLoginsPrompt": { + "message": "Pārskatīt riskam pakļautos pieteikšanās vienumus" + }, "dataLastUpdated": { "message": "Dati pēdējoreiz atjaunināti: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "būtiskas lietotnes atzīmētas" + }, "countOfCriticalApplications": { "message": "$COUNT$ būtiskas lietotnes", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "$ORG NAME$ netika atrasta neviena lietotne", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Ievieto savas apvienības pieteikšanās datus, lai uzsāktu to drošības risku uzraudzīšanu. Tiklīdz dati ir ievietoti, Tu vari:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Noteikt risku svarīgumu" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Pievērst uzmanību visbūtiskākajām lietotnēm" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Virzīt nepilnību novēršanu" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Piešķirt riskam pakļautajiem dalībniekiem uzdevumus riskam pakļauto pieteikšanās datu nomaiņai" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Pārraudzīt virzību" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Sekot izmaiņām, lai uzrādītu drošības uzlabojumus" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Izveidot savu pirmo atskaiti, lai redzētu lietotnes" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Izveidot riska ieskatu atskaiti, lai izvērtētu savas apvienības lietotnes un noteiktu riskam pakļautas paroles, kurēm jāpievērš uzmanība. Pirmās atskaites izveidošana:" }, "noCriticalApplicationsTitle": { "message": "Neviena lietotne nav atzīmēta kā kritiska" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Lietotnes, kas atzīmētas kā kritiskas" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ lietotnes atzīmētas kā būtiskas", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Neizdevās lietotnes atzīmēt kā būtiskas" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Dalībnieki ar piekluvi riskam pakļautajiem vienumiem būtiskajām lietotnēm" }, + "membersWithAtRiskPasswords": { + "message": "Dalībnieki ar riskam pakļautām parolēm" + }, + "membersWillReceiveNotification": { + "message": "Dalībnieki pārlūka paplašinājumā saņems paziņojumu, ka jānomaina riskam pakļautās paroles." + }, "membersAtRiskCount": { "message": "$COUNT$ dalībnieki ir pakļauti riskam", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Izskatīt tagad" }, + "allCaughtUp": { + "message": "Viss ir paveikts." + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Pašreiz nav jaunu izskatāmu lietotņu" + }, "prioritizeCriticalApplications": { "message": "Paaugstināt būtisko lietotņu svarīgumu" }, - "atRiskItems": { - "message": "Riskam pakļautie vienumi" + "selectCriticalApplicationsDescription": { + "message": "Jāatlasa, kuras lietontes apvienībai ir visbūtiskākās, tad jāpiešķir drošības uzdevumi dalībniekiem risku novēršanai." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Jāklikšķina uz zvaigznes, lai atzīmētu lietotni kā būtisku" }, "markAsCriticalPlaceholder": { "message": "Iespēja atzīmēt kā būtisku tiks ieviesta nākotnes atjauninājumā" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Lietotnes pārskats saglabāts" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Izskatītas jaunas lietotnes" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Kļūda izskatīšanas stāvokļa saglabāšanā" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Lūgums mēģināt vēlreiz" }, "unmarkAsCritical": { "message": "Noņemt kritiskuma atzīmi" @@ -835,6 +862,9 @@ "favorites": { "message": "Izlase" }, + "taskSummary": { + "message": "Uzdevuma kopsavilkums" + }, "types": { "message": "Veidi" }, @@ -1360,7 +1390,7 @@ "message": "Izmantot vienoto pieteikšanos" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tava apvienība pieprasa vienoto pieteikšanos." }, "welcomeBack": { "message": "Laipni lūdzam atpakaļ" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Atjaunināt pārlūku" }, - "generatingYourRiskInsights": { - "message": "Tiek veidots ieskats par riskiem..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Izveidot atskaiti" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Nepieciešama vienas vienīgas apvienības pamatnostādne. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Ikvienam, kurš ir daļa no vairāk kā vienas apvienības, tiks atsaukta piekļuve, līdz tiks pamestas pārējās apvienības." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Visiem dalībniekiem ir jābūt šajā apvienībā, lai aktivētu šo automatizēšanu." }, "autoConfirmSingleOrgExemption": { "message": "Vienas vienīgas apvienības pamatnostādne paplašināsies līdz visām lomām. " @@ -5872,6 +5902,19 @@ "message": "Vienmēr rādīt dalībnieka e-pasta adresi saņēmējiem, kad tiek izveidots vai labots Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Noklusējuma URI atbilstības noteikšana" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Nosaka, kad pieteikšanās vienumi tiek ieteikti automātiskajai aizpildei. Šī pamatnostādne neattiecas uz pārvaldītājiem un īpašniekiem." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Noklusējuma URI atbilstības noteikšana" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Lūgums atlasīt derīgu URI atbilstības noteikšanas iespēju.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Nosacījums $ID$ izmainīts.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Galvenā parole neatbilst vienam vai vairākiem apvienības nosacījumiem. Ir jāatjaunina galvenā parole, lai varētu piekļūt glabātavai. Turpinot notiks atteikšanās no pašreizējās sesijas, un būs nepieciešams pieteikties no jauna. Citās ierīcēs esošās sesijas var turpināt darboties līdz vienai stundai." }, - "automaticAppLogin": { - "message": "Automātiski pieteikt lietotājus atļautajās lietotnēs" + "automaticAppLoginWithSSO": { + "message": "Automātiska pieteikšanās ar SSO" }, - "automaticAppLoginDesc": { - "message": "Pieteikšanās veidlapas tiks automātiski aizpildītas un iesniegtas tajās lietotnēs, kuras ir palaistas no uzstādītā identitāšu nodrošinātāja." + "automaticAppLoginWithSSODesc": { + "message": "Izvērst SSO drošību un ērtumu nepārvaldītām lietotnēm. Kad lietotāji palaidīs lietotni no identitāšu nodrošinātāja, viņu pieteikšanās informācija tiks automātiski aizpildīta un iesniegta, izveidojot viena klikšķa drošu plūsmu no identitāšu nodrošinātāja uz lietotni." }, "automaticAppLoginIdpHostLabel": { "message": "Identitātes nodrošinātāja resursdators" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Jāpievieno vai nu servera pamata URL vai vismaz viena pielāgota vide." }, + "selfHostedEnvMustUseHttps": { + "message": "URL ir jābūt HTTPS." + }, "apiUrl": { "message": "API servera URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Piekļuve liegta. Nav nepieciešamo atļauju, lai apskatītu šo lapu." }, + "noPageAccess": { + "message": "Nav piekļuves šai lapai" + }, "masterPassword": { "message": "Galvenā parole" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Piešķirt uzdevumus" }, + "assignTasksToMembers": { + "message": "Piešķirt uzdevumus dalībniekiem risinājumam ar norādēm" + }, "assignToCollections": { "message": "Piešķirt krājumiem" }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index b087d181c52..1d892bdaa60 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "പ്രിയങ്കരങ്ങള്‍" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "തരങ്ങൾ" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "ബ്രൌസർ അപ്‌ഡേറ്റുചെയ്യുക" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 294713bbd17..5c65192e0b4 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "अ‍ॅक्सेस इंटेलिजेंस" }, - "riskInsights": { - "message": "जोखीम अंतर्दृष्टी" - }, "passwordRisk": { "message": "पासवर्डचा धोका" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 338f5c10d8a..e7051dee661 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 14ae5e59a88..c3f80105f40 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritter" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Typer" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Oppdater nettleseren" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Ikke tillat brukere å skjule sin e-postadresse fra mottakere når de oppretter eller endrer en Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modifisert policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API-tjenernettadresse" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Ingen tilgang. Du har ikke tillatelse til å se denne siden." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Hovedpassord" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Legg til i samlinger" }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 9ac3d615dfb..9da5012921d 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 051e9b616aa..4b68dfe4be6 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Toegangsintelligentie" }, - "riskInsights": { - "message": "Risicoinzichten" - }, "passwordRisk": { "message": "Wachtwoordrisico" }, + "noEditPermissions": { + "message": "Je hebt geen toestemming om dit item te bewerken" + }, "reviewAtRiskPasswords": { "message": "Herzie risicovolle wachtwoorden (zwak, blootgelegd of herbruikt) tussen applicaties. Selecteer je meest belangrijke applicaties om prioriteit te geven aan beveiligingsacties voor je gebruikers om risicovolle wachtwoorden aan te pakken." }, + "reviewAtRiskLoginsPrompt": { + "message": "Risicovolle logins bekijken" + }, "dataLastUpdated": { "message": "Datum laatste wijziging: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "belangrijke applicaties gemarkeerd" + }, "countOfCriticalApplications": { "message": "$COUNT$ belangrijke applicaties", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Als belangrijk gemarkeerde applicaties" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applicaties als belangrijk gemarkeerd", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Kon applicaties niet als kritiek markeren" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Leden met toegang tot risico-items voor belangrijke applicaties" }, + "membersWithAtRiskPasswords": { + "message": "Leden met risicovolle wachtwoorden" + }, + "membersWillReceiveNotification": { + "message": "Leden ontvangen een melding via de browserextensie om risico-logins op te lossen." + }, "membersAtRiskCount": { "message": "$COUNT$ leden lopen risico", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Nu beoordelen" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Belangrijke applicaties prioriteren" }, - "atRiskItems": { - "message": "Items in gevaar" + "selectCriticalApplicationsDescription": { + "message": "Selecteer welke toepassingen het meest belangrijk zijn voor je organisatie en wijs de leden dan beveiligingstaken toe om risico's op te lossen." + }, + "reviewNewApplications": { + "message": "Nieuwe toepassingen beoordelen" + }, + "reviewNewApplicationsDescription": { + "message": "We hebben items met risico gemarkeerd voor nieuwe toepassingen die zijn opgeslagen in Admin-console die zwakke, onthulde of hergebruikte wachtwoorden hebben." + }, + "clickIconToMarkAppAsCritical": { + "message": "Klik op het sterpictogram om een app als belangrijk te markeren" }, "markAsCriticalPlaceholder": { "message": "Markeren als kritieke functionaliteit wordt geïmplementeerd in een toekomstige update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorieten" }, + "taskSummary": { + "message": "Taaksamenvatting" + }, "types": { "message": "Categorieën" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Webbrowser bijwerken" }, - "generatingYourRiskInsights": { - "message": "Je risico-inzichten genereren..." + "generatingYourAccessIntelligence": { + "message": "Je toegangsinlichtingen genereren..." }, "riskInsightsRunReport": { "message": "Rapport uitvoeren" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Enkele Organisatie-beleid vereist " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Iedereen die deel uitmaakt van meer dan één organisatie krijgt zijn toegang ingetrokken totdat deze de andere organisaties verlaat." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Alle leden mogen alleen lid zijn van deze organisatie om deze automatisering te activeren." }, "autoConfirmSingleOrgExemption": { "message": "Het beleid inzake één organisatie strekt zich uit tot alle rollen. " @@ -5872,6 +5902,19 @@ "message": "Gebruikers mogen hun e-mailadres niet verbergen voor ontvangers bij het aanmaken of bewerken van een Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Standaard URI-overeenkomstdetectie" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Bepaal wanneer logins voor autofill worden voorgesteld. Beheerders en eigenaren zijn vrijgesteld van dit beleid." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Standaard URI-overeenkomstdetectie" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Selecteer een geldige optie voor URI match-detectie.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Bewerkt beleid $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Je hoofdwachtwoord voldoet niet aan en of meerdere oganisatiebeleidsonderdelen. Om toegang te krijgen tot de kluis, moet je je hoofdwachtwoord nu bijwerken. Doorgaan zal je huidige sessie uitloggen, waarna je opnieuw moet inloggen. Actieve sessies op andere apparaten blijven mogelijk nog een uur actief." }, - "automaticAppLogin": { - "message": "Gebruikers automatisch inloggen voor toegestane applicaties" + "automaticAppLoginWithSSO": { + "message": "Automatische login met SSO" }, - "automaticAppLoginDesc": { - "message": "Inlogformulieren automatisch invullen en indienen voor apps die zijn gestart vanuit je geconfigureerde identiteitsprovider." + "automaticAppLoginWithSSODesc": { + "message": "Bereid het gemak en beveiliging van SSO uit naar niet-beheerde apps. Wanneer gebruikers een app starten van je identiteitsprovider, worden de inloggegevens automatisch ingevuld en verzonden, voor een één-klik, beveiligde flow van de identiteitsprovider naar de app." }, "automaticAppLoginIdpHostLabel": { "message": "Identiteitsproviderhost" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, + "selfHostedEnvMustUseHttps": { + "message": "URL's moeten HTTPS gebruiken." + }, "apiUrl": { "message": "API-server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Toegang geweigerd. Je hebt geen toestemming om deze pagina te bekijken." }, + "noPageAccess": { + "message": "Je hebt geen toegang tot deze pagina" + }, "masterPassword": { "message": "Hoofdwachtwoord" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Taken toewijzen" }, + "assignTasksToMembers": { + "message": "Taken aan leden toewijzen voor een begeleide oplossing" + }, "assignToCollections": { "message": "Toewijzen aan collecties" }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index f0b0a82da9c..24845833d22 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorittar" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Typar" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 338f5c10d8a..e7051dee661 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index d81146ad284..4d450fa7543 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Dostęp do informacji" }, - "riskInsights": { - "message": "Spostrzeżenia dotyczące ryzyka" - }, "passwordRisk": { "message": "Ryzyko związne z hasłem" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Przejrzyj hasła zagrożone (słabe, ujawnione lub ponownie używane) we wszystkich aplikacjach. Wybierz swoje najbardziej krytyczne aplikacje, aby nadać priorytet działaniom bezpieczeństwa swoim użytkownikom, aby zająć się hasłami zagrożonymi." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data ostatniej aktualizacji: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplikacje oznaczone jako krytyczne" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Ulubione" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Rodzaje" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Aktualizuj przeglądarkę" }, - "generatingYourRiskInsights": { - "message": "Generowanie informacji o ryzyku..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Nie zezwalaj użytkownikom na ukrywanie ich adresów e-mail przed odbiorcami, podczas tworzenia lub edytowania wysyłek.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Zasada $ID$ została zaktualizowana.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Twoje hasło główne nie spełnia jednej lub kilku zasad organizacji. Aby uzyskać dostęp do sejfu, musisz teraz zaktualizować swoje hasło główne. Kontynuacja wyloguje Cię z bieżącej sesji, wymagając zalogowania się ponownie. Aktywne sesje na innych urządzeniach mogą pozostać aktywne przez maksymalnie jedną godzinę." }, - "automaticAppLogin": { - "message": "Automatycznie zaloguj użytkowników dla dozwolonych aplikacji" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Formularze logowania zostaną automatycznie wypełnione i przesłane dla aplikacji uruchomionych od skonfigurowanego dostawcy tożsamości." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Host dostawcy tożsamości" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "Adres URL serwera API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Odmowa dostępu. Nie masz uprawnień do przeglądania tej strony." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Hasło główne" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Przypisz do kolekcji" }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index fdcbc0e27c9..7d4edcecf5f 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Inteligência de acesso" }, - "riskInsights": { - "message": "Critérios de risco" - }, "passwordRisk": { "message": "Risco de senhas" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Revise senhas em risco (fracas, expostas, ou reutilizadas) em todos os aplicativos. Selecione seus aplicativos mais críticos para priorizar ações de segurança para que seus usuários resolvem senhas em risco." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Última atualização dos dados: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ aplicativos críticos", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplicativos marcados como críticos" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Falha ao marcar aplicativos como críticos" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Membros com acesso a itens em risco de aplicativos críticos" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Revisar agora" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Priorizar aplicativos críticos" }, - "atRiskItems": { - "message": "Itens em risco" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "A funcionalidade de marcar como crítico será implementada em uma atualização futura" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritos" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipos" }, @@ -1339,7 +1369,7 @@ "message": "Precisa de outra opção?" }, "loginWithMasterPassword": { - "message": "Entrar com senha mestre" + "message": "Entrar com senha principal" }, "readingPasskeyLoading": { "message": "Lendo a chave de acesso..." @@ -1357,7 +1387,7 @@ "message": "Entrar com chave de acesso" }, "useSingleSignOn": { - "message": "Usar login único" + "message": "Usar autenticação única" }, "yourOrganizationRequiresSingleSignOn": { "message": "Your organization requires single sign-on." @@ -1402,7 +1432,7 @@ "message": "Usar para criptografia do cofre" }, "useForVaultEncryptionInfo": { - "message": "Conecte-se e desbloqueie em dispositivos suportados sem a sua senha mestre. Siga as instruções do seu navegador para finalizar a configuração." + "message": "Conecte-se e desbloqueie em dispositivos suportados sem a sua senha principal. Siga as instruções do seu navegador para finalizar a configuração." }, "useForVaultEncryptionErrorReadingPasskey": { "message": "Erro ao ler a chave de acesso. Tente novamente ou desmarque esta opção." @@ -1435,7 +1465,7 @@ "message": "Remover chave de acesso" }, "removePasskeyInfo": { - "message": "Se todas as chaves de acesso forem removidas, não será mais possível entrar em novos dispositivos sem sua senha mestre." + "message": "Se todas as chaves de acesso forem removidas, não será mais possível entrar em novos dispositivos sem sua senha principal." }, "passkeyLimitReachedInfo": { "message": "Limite de chaves de acesso atingido. Remova uma chave de acesso para adicionar outra." @@ -1516,28 +1546,28 @@ "message": "Como devemos chamá-lo?" }, "masterPass": { - "message": "Senha mestre" + "message": "Senha principal" }, "masterPassDesc": { - "message": "A senha mestra é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha mestra. Não há maneira de recuperar a senha caso você se esqueça." + "message": "A senha principal é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha principal. Não há maneira de recuperar a senha caso você se esqueça." }, "masterPassImportant": { - "message": "Sua senha mestre não pode ser recuperada se esquecê-la!" + "message": "Sua senha principal não pode ser recuperada se esquecê-la!" }, "masterPassHintDesc": { - "message": "Uma dica de senha mestra pode ajudá-lo(a) a lembrar a senha caso você esqueça." + "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrar a senha caso você esqueça." }, "reTypeMasterPass": { - "message": "Digite novamente a senha mestre" + "message": "Digite novamente a senha principal" }, "masterPassHint": { - "message": "Dica da senha mestre (opcional)" + "message": "Dica da senha principal (opcional)" }, "newMasterPassHint": { - "message": "Nova dica de senha mestre (opcional)" + "message": "Nova dica de senha principal (opcional)" }, "masterPassHintLabel": { - "message": "Dica da senha mestre" + "message": "Dica da senha principal" }, "masterPassHintText": { "message": "Se você esquecer sua senha, a dica da senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", @@ -1562,13 +1592,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha mestre" + "message": "Dica da senha principal" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e sua dica da senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha mestra" + "message": "Obter dica da senha principal" }, "emailRequired": { "message": "O endereço de e-mail é obrigatório." @@ -1577,13 +1607,13 @@ "message": "Endereço de e-mail inválido." }, "masterPasswordRequired": { - "message": "A senha mestra é obrigatória." + "message": "A senha principal é obrigatória." }, "confirmMasterPasswordRequired": { - "message": "A senha mestre é obrigatória." + "message": "A senha principal é obrigatória." }, "masterPasswordMinlength": { - "message": "A senha mestre deve ter pelo menos $VALUE$ caracteres.", + "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -1593,7 +1623,7 @@ } }, "masterPassDoesntMatch": { - "message": "A confirmação da senha mestra não corresponde." + "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." @@ -1608,7 +1638,7 @@ "message": "Conta criada com sucesso." }, "masterPassSent": { - "message": "Enviamos um e-mail com a dica da sua senha mestra." + "message": "Enviamos um e-mail com a dica da sua senha principal." }, "unexpectedError": { "message": "Ocorreu um erro inesperado." @@ -1645,10 +1675,10 @@ } }, "invalidMasterPassword": { - "message": "Senha mestra inválida" + "message": "Senha principal inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Senha mestre inválida. Confirme se seu e-mail está correto e sua conta foi criada em $HOST$.", + "message": "Senha principal inválida. Confirme se seu e-mail está correto e sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -1949,7 +1979,7 @@ "message": "Esta senha será usada para exportar e importar este arquivo" }, "confirmMasterPassword": { - "message": "Confirme a senha mestre" + "message": "Confirme a senha principal" }, "confirmFormat": { "message": "Confirmar formato" @@ -1961,7 +1991,7 @@ "message": "Confirmar senha do arquivo" }, "accountRestrictedOptionDescription": { - "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha mestre da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." + "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtectedOptionDescription": { "message": "Defina uma senha para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografar." @@ -2120,19 +2150,19 @@ "message": "Por favor, reinicie a sessão. Se estiver usando outros aplicativos do Bitwarden, encerre a sessão e reinicie também." }, "changeMasterPassword": { - "message": "Alterar senha mestre" + "message": "Alterar senha principal" }, "masterPasswordChanged": { - "message": "Senha mestre salva" + "message": "Senha principal salva" }, "currentMasterPass": { - "message": "Senha mestre atual" + "message": "Senha principal atual" }, "newMasterPass": { - "message": "Nova senha mestre" + "message": "Nova senha principal" }, "confirmNewMasterPass": { - "message": "Confirmar nova senha mestra" + "message": "Confirmar nova senha principal" }, "encKeySettings": { "message": "Configurações da chave de criptografia" @@ -2141,7 +2171,7 @@ "message": "Iterações da KDF" }, "kdfIterationsDesc": { - "message": "As iterações KDF mais altas podem ajudar a proteger a sua senha mestra de ser descoberta pela força bruta de um invasor. Recomendamos um valor de $VALUE$ ou mais.", + "message": "As iterações KDF mais altas podem ajudar a proteger a sua senha principal de ser descoberta pela força bruta de um invasor. Recomendamos um valor de $VALUE$ ou mais.", "placeholders": { "value": { "content": "$1", @@ -2169,7 +2199,7 @@ "message": "Paralelismo da KDF" }, "argon2Desc": { - "message": "Mais iterações KDF, memória e paralelismo podem ajudar a proteger sua senha mestre de ser descoberta por força bruta por um invasor." + "message": "Mais iterações KDF, memória e paralelismo podem ajudar a proteger sua senha principal de ser descoberta por força bruta por um invasor." }, "encKeySettingsChanged": { "message": "As configurações da chave de criptografia foram salvas" @@ -2202,7 +2232,7 @@ "message": "Prossiga abaixo para que o Bitwarden envie e-mails de verificação quando você se conectar a partir de um novo dispositivo." }, "turnOffNewDeviceLoginProtectionWarning": { - "message": "Com a proteção de autenticação de dispositivos novos desativada, qualquer um com a sua senha mestre pode acessar sua conta de qualquer dispositivo. Para proteger sua conta sem e-mails de verificação, configure a autenticação em duas etapas." + "message": "Com a proteção de autenticação de dispositivos novos desativada, qualquer um com a sua senha principal pode acessar sua conta de qualquer dispositivo. Para proteger sua conta sem e-mails de verificação, configure a autenticação em duas etapas." }, "accountNewDeviceLoginProtectionSaved": { "message": "Alterações na proteção na entrada de dispositivos novos salvas" @@ -2547,7 +2577,7 @@ "message": "Este provedor de autenticação em duas etapas está ativo em sua conta." }, "twoStepLoginAuthDesc": { - "message": "Insira a sua senha mestra para modificar as configurações de login em duas etapas." + "message": "Insira a sua senha principal para modificar as configurações de login em duas etapas." }, "twoStepAuthenticatorInstructionPrefix": { "message": "Baixe um app autenticador como o" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Atualize o navegador" }, - "generatingYourRiskInsights": { - "message": "Gerando suas informações de risco..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Executar relatório" @@ -4492,7 +4522,7 @@ "message": "Você foi convidado para participar da organização listada acima. Para aceitar o convite, você precisa iniciar sessão ou criar uma nova conta no Bitwarden." }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se nessa organização definindo uma senha mestre." + "message": "Termine de juntar-se nessa organização definindo uma senha principal." }, "inviteAccepted": { "message": "Convite aceito" @@ -4555,7 +4585,7 @@ "message": "Apagar organização" }, "deletingOrganizationContentWarning": { - "message": "Digite a senha mestre para confirmar o apagamento de $ORGANIZATION$ e todos os dados associados. Os dados do cofre no $ORGANIZATION$ incluem:", + "message": "Digite a senha principal para confirmar o apagamento de $ORGANIZATION$ e todos os dados associados. Os dados do cofre no $ORGANIZATION$ incluem:", "placeholders": { "organization": { "content": "$1", @@ -5034,10 +5064,10 @@ "description": "ex. A very weak password. Scale: Very Weak -> Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Senha mestre fraca" + "message": "Senha principal fraca" }, "weakMasterPasswordDesc": { - "message": "Senha fraca identificada. Você deve usar uma senha mestra forte para proteger a sua conta. Tem certeza que quer usar esta senha mestre?" + "message": "Senha fraca identificada. Você deve usar uma senha principal forte para proteger a sua conta. Tem certeza que quer usar esta senha principal?" }, "rotateAccountEncKey": { "message": "Também rodar a chave de encriptação da minha conta" @@ -5127,10 +5157,10 @@ "message": "Clonar" }, "masterPassPolicyTitle": { - "message": "Requisitos de senha mestra" + "message": "Requisitos de senha principal" }, "masterPassPolicyDesc": { - "message": "Defina os requisitos para a força da senha mestre." + "message": "Defina os requisitos para a força da senha principal." }, "passwordStrengthScore": { "message": "Pontuação de força da senha $SCORE$", @@ -5157,7 +5187,7 @@ "message": "Defina requisitos para o gerador de senhas." }, "masterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a sua senha mestra cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a sua senha principal cumpra aos seguintes requisitos:" }, "policyInEffectMinComplexity": { "message": "Pontuação mínima de complexidade de $SCORE$", @@ -5196,7 +5226,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "A sua nova senha mestra não cumpre aos requisitos da política." + "message": "A sua nova senha principal não cumpre aos requisitos da política." }, "minimumNumberOfWords": { "message": "Número mínimo de palavras" @@ -5212,7 +5242,7 @@ "message": "Ação do tempo limite do cofre" }, "vaultTimeoutActionLockDesc": { - "message": "A senha mestre ou outro método de desbloqueio é necessário para acessar seu cofre novamente." + "message": "A senha principal ou outro método de desbloqueio é necessário para acessar seu cofre novamente." }, "vaultTimeoutActionLogOutDesc": { "message": "Reautenticação é necessária para acessar seu cofre novamente." @@ -5307,7 +5337,7 @@ "message": "Informações de impostos atualizadas." }, "setMasterPassword": { - "message": "Configurar senha mestre" + "message": "Configurar senha principal" }, "identifier": { "message": "Identificador" @@ -5628,7 +5658,7 @@ "message": "Assumir controle" }, "takeoverDesc": { - "message": "Pode redefinir a sua conta com uma nova senha mestre." + "message": "Pode redefinir a sua conta com uma nova senha principal." }, "waitTime": { "message": "Tempo de espera" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Sempre mostrar o endereço de e-mail do membro com destinatários ao criar ou editar um Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modificou a política $ID$.", "placeholders": { @@ -6180,7 +6223,7 @@ } }, "eventAdminPasswordReset": { - "message": "Senha mestre do usuário $ID$ redefinida.", + "message": "Senha principal do usuário $ID$ redefinida.", "placeholders": { "id": { "content": "$1", @@ -6231,25 +6274,25 @@ "message": "este usuário" }, "resetPasswordMasterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a senha mestre cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a senha principal cumpra aos seguintes requisitos:" }, "changePasswordDelegationMasterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a senha mestre cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a senha principal cumpra aos seguintes requisitos:" }, "resetPasswordSuccess": { "message": "Senha redefinida com sucesso!" }, "resetPasswordEnrollmentWarning": { - "message": "A inscrição permitirá que os administradores da organização alterem sua senha mestra. Tem certeza que deseja se inscrever?" + "message": "A inscrição permitirá que os administradores da organização alterem sua senha principal. Tem certeza que deseja se inscrever?" }, "accountRecoveryPolicy": { "message": "Gerenciar recuperação de conta" }, "accountRecoveryPolicyDesc": { - "message": "Baseado no método de criptografia, recupere contas quando senhas mestras ou dispositivos confiáveis forem esquecidos ou perdidos." + "message": "Baseado no método de criptografia, recupere contas quando senhas principais ou dispositivos confiáveis forem esquecidos ou perdidos." }, "accountRecoveryPolicyWarning": { - "message": "Contas existentes com senhas mestras exigirá que os membros se inscrevam antes que os administradores possam recuperar suas contas. A inscrição automática irá ativar a recuperação de conta para novos membros." + "message": "Contas existentes com senhas principais exigirá que os membros se inscrevam antes que os administradores possam recuperar suas contas. A inscrição automática irá ativar a recuperação de conta para novos membros." }, "accountRecoverySingleOrgRequirementDesc": { "message": "A política empresarial de organização única deve ser ativada antes de ativar esta política." @@ -6261,7 +6304,7 @@ "message": "Inscrever automaticamente novos usuários" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha mestra." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "resetPasswordOrgKeysError": { "message": "A resposta das Chaves da Organização é nula" @@ -6276,13 +6319,13 @@ "message": "As cifras que estiverem na Lixeira por um tempo serão excluídas automaticamente." }, "passwordPrompt": { - "message": "Nova solicitação de senha mestra" + "message": "Nova solicitação de senha principal" }, "passwordConfirmation": { - "message": "Confirmação de senha mestra" + "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha mestra para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." }, "reinviteSelected": { "message": "Reenviar Convites" @@ -6523,13 +6566,13 @@ "message": "Sua Senha Mestra foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizar sua Senha Mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, - "automaticAppLogin": { - "message": "Fazer login automaticamente em usuários para aplicativos permitidos" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Os formulários de login serão automaticamente preenchidos e enviados para aplicativos iniciados por seu provedor de identidade configurado." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Provedor de identidade" @@ -6538,7 +6581,7 @@ "message": "Digite a URL do seu provedor de identidade. Insira vários URLs, separando com uma vírgula." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização atualizou suas opções de descriptografia. Por favor, defina uma senha mestra para acessar seu cofre." + "message": "Sua organização atualizou suas opções de descriptografia. Por favor, defina uma senha principal para acessar seu cofre." }, "sessionTimeoutPolicyTitle": { "message": "Session timeout" @@ -6983,7 +7026,7 @@ "message": "Remover Senha Mestra" }, "removedMasterPassword": { - "message": "Senha mestra removida." + "message": "Senha principal removida." }, "allowSso": { "message": "Permitir autenticação por SSO" @@ -7013,7 +7056,7 @@ "message": "Conector de Chave" }, "memberDecryptionKeyConnectorDescStart": { - "message": "Conecte o login com SSO ao seu servidor de chave de descriptografia auto-hospedado. Usando essa opção, os membros não precisarão usar suas senhas mestres para descriptografar os dados", + "message": "Conecte o login com SSO ao seu servidor de chave de descriptografia auto-hospedado. Usando essa opção, os membros não precisarão usar suas senhas principais para descriptografar os dados", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members won’t need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'" }, "memberDecryptionKeyConnectorDescLink": { @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL do servidor API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Acesso negado. Você não tem permissão para ver esta página." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Senha Mestra" }, @@ -8475,7 +8524,7 @@ "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha mestra" + "message": "Usar a senha principal" }, "usePin": { "message": "Usar PIN" @@ -8625,7 +8674,7 @@ "message": "Desbloquear com o PIN" }, "unlockWithMasterPassword": { - "message": "Desbloquear com a senha mestra" + "message": "Desbloquear com a senha principal" }, "licenseAndBillingManagement": { "message": "Gerenciamento da licença e faturamento" @@ -9026,7 +9075,7 @@ "message": "Verificar se essa senha aparece em vazamento de dados conhecidos" }, "exposedMasterPassword": { - "message": "Senha mestre exposta" + "message": "Senha principal exposta" }, "exposedMasterPasswordDesc": { "message": "A senha foi encontrada em violação de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha exposta?" @@ -9047,7 +9096,7 @@ } }, "masterPasswordMinimumlength": { - "message": "A senha mestra deve ter pelo menos $LENGTH$ caracteres.", + "message": "A senha principal deve ter pelo menos $LENGTH$ caracteres.", "placeholders": { "length": { "content": "$1", @@ -9123,7 +9172,7 @@ "message": "Dispositivos confiáveis" }, "memberDecryptionOptionTdeDescPart1": { - "message": "Os membros não precisarão de uma senha mestra ao fazer login com SSO. A senha mestra é substituída por uma chave de criptografia armazenada no dispositivo, fazendo com que esse dispositivo seja confiável. O primeiro dispositivo que um membro cria sua conta e os logins serão confiáveis. Novos dispositivos precisarão ser aprovados por um dispositivo confiável existente ou por um administrador. O", + "message": "Os membros não precisarão de uma senha principal ao fazer login com SSO. A senha principal é substituída por uma chave de criptografia armazenada no dispositivo, fazendo com que esse dispositivo seja confiável. O primeiro dispositivo que um membro cria sua conta e os logins serão confiáveis. Novos dispositivos precisarão ser aprovados por um dispositivo confiável existente ou por um administrador. O", "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "memberDecryptionOptionTdeDescLink1": { @@ -9151,11 +9200,11 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Members will not need a master password when logging in with SSO. Master password is replaced with an encryption key stored on the device, making that device trusted. The first device a member creates their account and logs into will be trusted. New devices will need to be approved by an existing trusted device or by an administrator. The single organization policy, SSO required policy, and account recovery administration policy will turn on when this option is used.'" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha mestra.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha mestra.", + "message": "Sua organização requer que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -9253,10 +9302,10 @@ "message": "Solicitação de login aprovada" }, "removeOrgUserNoMasterPasswordTitle": { - "message": "A conta não tem uma senha mestre" + "message": "A conta não tem uma senha principal" }, "removeOrgUserNoMasterPasswordDesc": { - "message": "Remover $USER$ sem definir uma senha mestra pode restringir o acesso deles à conta toda. Tem certeza de que deseja continuar?", + "message": "Remover $USER$ sem definir uma senha principal pode restringir o acesso deles à conta toda. Tem certeza de que deseja continuar?", "placeholders": { "user": { "content": "$1", @@ -9265,10 +9314,10 @@ } }, "noMasterPassword": { - "message": "Nenhuma senha mestre" + "message": "Nenhuma senha principal" }, "removeMembersWithoutMasterPasswordWarning": { - "message": "Remover membros que não têm senhas mestres sem definir uma para eles pode restringir o acesso à sua conta completa." + "message": "Remover membros que não têm senhas principais sem definir uma para eles pode restringir o acesso à sua conta completa." }, "approvedAuthRequest": { "message": "Dispositivo aprovado para $ID$.", @@ -9292,7 +9341,7 @@ "message": "Aprovação do dispositivo solicitada." }, "tdeOffboardingPasswordSet": { - "message": "Usuário definiu uma senha mestra durante o offboard TDE." + "message": "Usuário definiu uma senha principal durante o offboard TDE." }, "startYour7DayFreeTrialOfBitwardenFor": { "message": "Comece o seu período de teste gratuito de 7 dias do Bitwarden para $ORG$", @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Atribuir à coleções" }, @@ -10057,7 +10109,7 @@ "message": "Excluir Provedor" }, "deleteProviderConfirmation": { - "message": "A exclusão de um provedor é permanente e irreversível. Digite sua senha mestra para confirmar a exclusão do provedor e de todos os dados associados." + "message": "A exclusão de um provedor é permanente e irreversível. Digite sua senha principal para confirmar a exclusão do provedor e de todos os dados associados." }, "deleteProviderName": { "message": "Não é possível excluir $ID$", @@ -10654,7 +10706,7 @@ "message": "Saiba mais sobre a detecção de partidas" }, "learnMoreAboutMasterPasswordReprompt": { - "message": "Saiba mais sobre a redefinição da senha mestra" + "message": "Saiba mais sobre a redefinição da senha principal" }, "learnMoreAboutSearchingYourVault": { "message": "Saiba mais sobre como pesquisar no seu cofre" @@ -11162,7 +11214,7 @@ "message": "Rotação de chave bem-sucedida" }, "rotationCompletedDesc": { - "message": "Sua senha mestra e chave de criptografia foram atualizadas. Seus outros dispositivos foram desconectados." + "message": "Sua senha principal e chave de criptografia foram atualizadas. Seus outros dispositivos foram desconectados." }, "trustUserEmergencyAccess": { "message": "Confiar e confirmar usuário" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 972fad28bd5..09e346b494f 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Aceder à informação" }, - "riskInsights": { - "message": "Perceções do risco" - }, "passwordRisk": { "message": "Risco da palavra-passe" }, + "noEditPermissions": { + "message": "Não tem permissão para editar este item" + }, "reviewAtRiskPasswords": { "message": "Reveja as palavras-passe em risco (fracas, expostas ou reutilizadas) em todas as aplicações. Selecione as suas aplicações mais críticas para dar prioridade a ações de segurança para os seus utilizadores para resolver as palavras-passe em risco." }, + "reviewAtRiskLoginsPrompt": { + "message": "Rever credenciais em risco" + }, "dataLastUpdated": { "message": "Última atualização dos dados: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "aplicações críticas marcadas" + }, "countOfCriticalApplications": { "message": "$COUNT$ aplicações críticas", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplicações marcadas como críticas" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ marcadas como críticas", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Falha ao marcar aplicações como críticas" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Membros com acesso a itens em risco para aplicações críticas" }, + "membersWithAtRiskPasswords": { + "message": "Membros com palavras-passe em risco" + }, + "membersWillReceiveNotification": { + "message": "Os membros receberão uma notificação para resolver as credenciais em risco através da extensão do navegador." + }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Analisar agora" }, + "allCaughtUp": { + "message": "Tudo em dia!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Não há novas aplicações para rever neste momento" + }, "prioritizeCriticalApplications": { "message": "Dê prioridade a aplicações críticas" }, - "atRiskItems": { - "message": "Itens em risco" + "selectCriticalApplicationsDescription": { + "message": "Selecione quais aplicações são mais críticas para a sua organização e, em seguida, atribua tarefas de segurança aos membros para resolver os riscos." + }, + "reviewNewApplications": { + "message": "Rever novas aplicações" + }, + "reviewNewApplicationsDescription": { + "message": "Destacámos os itens em risco para novas aplicações armazenadas na consola de administração que têm palavras-passe fracas, expostas ou reutilizadas." + }, + "clickIconToMarkAppAsCritical": { + "message": "Clique no ícone de estrela para marcar uma app como crítica" }, "markAsCriticalPlaceholder": { "message": "A funcionalidade marcada como crítica será implementada numa atualização futura" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Revisão da aplicação guardada" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ marcadas como críticas", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "Novas aplicações revistas" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favoritos" }, + "taskSummary": { + "message": "Resumo da tarefa" + }, "types": { "message": "Tipos" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Atualizar navegador" }, - "generatingYourRiskInsights": { - "message": "A gerar as suas perceções de riscos..." + "generatingYourAccessIntelligence": { + "message": "A gerar a sua Inteligência de Acesso..." }, "riskInsightsRunReport": { "message": "Executar relatório" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "É necessária uma política de organização única. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Qualquer pessoa que faça parte de mais de uma organização terá o seu acesso revogado até que saia das outras organizações." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Todos os membros devem pertencer exclusivamente a esta organização para ativar esta automação." }, "autoConfirmSingleOrgExemption": { "message": "A política de organização única será estendida a todos os papéis. " @@ -5872,6 +5902,19 @@ "message": "Mostrar sempre o endereço de e-mail do membro com os destinatários ao criar ou editar um Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Deteção de correspondência de URI predefinida" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine quando as credenciais são sugeridas para preenchimento automático. Administradores e proprietários estão isentos desta política." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Deteção de correspondência de URI predefinida" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Por favor, selecione uma opção válida de deteção de correspondência de URI.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Política $ID$ modificada.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "A sua palavra-passe mestra não cumpre uma ou mais políticas da sua organização. Para aceder ao cofre, tem de atualizar a sua palavra-passe mestra agora. Ao prosseguir, terminará a sua sessão atual e terá de iniciar sessão novamente. As sessões ativas noutros dispositivos poderão continuar ativas até uma hora." }, - "automaticAppLogin": { - "message": "Iniciar automaticamente a sessão dos utilizadores nas aplicações permitidas" + "automaticAppLoginWithSSO": { + "message": "Início de sessão automático com SSO" }, - "automaticAppLoginDesc": { - "message": "Os formulários de início de sessão serão automaticamente preenchidos e submetidos para aplicações lançadas a partir do seu fornecedor de identidade configurado." + "automaticAppLoginWithSSODesc": { + "message": "Estenda a segurança e a conveniência do SSO para apps não geridas. Quando os utilizadores iniciam uma aplicação a partir do seu fornecedor de identidade, as suas credenciais são preenchidas e enviadas automaticamente, criando um fluxo seguro com um clique do fornecedor de identidade para a app." }, "automaticAppLoginIdpHostLabel": { "message": "Anfitrião do fornecedor de identidade" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "Os URLs devem usar HTTPS." + }, "apiUrl": { "message": "URL do servidor da API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Acesso negado. Não tem permissão para ver esta página." }, + "noPageAccess": { + "message": "Não tem acesso a esta página" + }, "masterPassword": { "message": "Palavra-passe mestra" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Atribuir tarefas" }, + "assignTasksToMembers": { + "message": "Atribuir tarefas aos membros para resolução guiada" + }, "assignToCollections": { "message": "Atribuir às coleções" }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 8793e1dc632..3a3e8846c66 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Risc Parola" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplicații marcate drept critice" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorite" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipuri" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Actualizare browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Afișați întotdeauna adresa de e-mail a membrului împreună cu destinatarii atunci când creați sau editați un Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Politica $ID$ a fost editată.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Acces refuzat. Nu aveți permisiunea de a vizualiza această pagină." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Parola principală" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 08f36143fc7..31a1729cef8 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Информация о рисках" - }, "passwordRisk": { "message": "Риск пароля" }, + "noEditPermissions": { + "message": "У вас нет разрешения на редактирование этого элемента" + }, "reviewAtRiskPasswords": { "message": "Проанализируйте пароли, подверженные риску (слабые, скомпрометированные или повторно используемые), во всех приложениях. Выберите наиболее критичные приложения, чтобы определить приоритетные меры безопасности для пользователей, направленные на устранение подверженных риску паролей." }, + "reviewAtRiskLoginsPrompt": { + "message": "Обзор логинов, подверженных риску" + }, "dataLastUpdated": { "message": "Последнее обновление: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "критичные приложения помечены" + }, "countOfCriticalApplications": { "message": "$COUNT$ критичных приложений", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Приложения помечены как критичные" }, + "criticalApplicationsMarkedSuccess": { + "message": "Помечены как критичные приложений: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Не удалось пометить приложения как критичные" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Участники, имеющие доступ к элементам, подверженным риску, для критичных приложений" }, + "membersWithAtRiskPasswords": { + "message": "Пользователи, пароли которых подверженны риску" + }, + "membersWillReceiveNotification": { + "message": "Пользователи получат уведомление о разрешении подверженных риску логинов с помощью расширения браузера." + }, "membersAtRiskCount": { "message": "$COUNT$ участников, подверженных риску", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Проверить сейчас" }, + "allCaughtUp": { + "message": "Все сделано!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "На данный момент новых приложений для рассмотрения нет" + }, "prioritizeCriticalApplications": { "message": "Приоритет критичных приложений" }, - "atRiskItems": { - "message": "Элементы, подверженные риску" + "selectCriticalApplicationsDescription": { + "message": "Выберите, какие приложения наиболее критичны для вашей организации, а затем назначьте пользователям задачи по обеспечению безопасности для устранения рисков." + }, + "reviewNewApplications": { + "message": "Обзор новых приложений" + }, + "reviewNewApplicationsDescription": { + "message": "Мы выделили элементы, подверженные риску, для новых приложений, хранящихся в консоли администратора, которые имеют слабые, незащищенные или повторно используемые пароли." + }, + "clickIconToMarkAppAsCritical": { + "message": "Нажмите на значок звездочки, чтобы отметить приложение как критичное" }, "markAsCriticalPlaceholder": { "message": "Функционал отметки как критического будет реализован в будущем обновлении" @@ -355,20 +391,11 @@ "applicationReviewSaved": { "message": "Обзор приложения сохранен" }, - "applicationsMarkedAsCritical": { - "message": "Помечены как критичные приложений: $COUNT$", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "Рассмотрены новые заявки" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Ошибка сохранения статуса обзора" }, "pleaseTryAgain": { "message": "Попробуйте еще раз" @@ -835,6 +862,9 @@ "favorites": { "message": "Избранные" }, + "taskSummary": { + "message": "Сводка по задаче" + }, "types": { "message": "Типы элементов" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Обновить браузер" }, - "generatingYourRiskInsights": { - "message": "Генерация информации о рисках..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Запустить отчет" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Требуется политика единой организации. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "У любого сотрудника, работающего в нескольких организациях, будет отозван доступ до тех пор, пока он не покинет другие организации." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Все участники должны принадлежать только этой организации для активации этой автоматизации." }, "autoConfirmSingleOrgExemption": { "message": "Политика единой организации распространяется на все роли. " @@ -5872,6 +5902,19 @@ "message": "Всегда показывать email пользователя получателям при создании или редактировании Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Обнаружение совпадения URI по умолчанию" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Определяет, когда логины будут предлагаться для автозаполнения. Администраторы и владельцы освобождаются от этой политики." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Обнаружение совпадения URI по умолчанию" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Пожалуйста, выберите допустимый вариант определения соответствия URI.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Изменена политика $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш мастер-пароль не соответствует требованиям политики вашей организации. Для доступа к хранилищу вы должны обновить свой мастер-пароль прямо сейчас. При этом текущий сеанс будет завершен и потребуется повторная авторизация. Сеансы на других устройствах могут оставаться активными в течение часа." }, - "automaticAppLogin": { - "message": "Автоматическая авторизация пользователей в разрешенные приложения" + "automaticAppLoginWithSSO": { + "message": "Автовход с помощью SSO" }, - "automaticAppLoginDesc": { - "message": "Формы авторизаций будут автоматически заполняться и отправляться для приложений, запущенных с помощью настроенного провайдера идентификационных данных." + "automaticAppLoginWithSSODesc": { + "message": "Повысьте безопасность и удобство единого входа в неуправляемые приложения. Когда пользователи запускают приложение у вашего провайдера идентификации, их регистрационные данные автоматически заполняются и отправляются, создавая безопасный поток от провайдера к приложению в один клик." }, "automaticAppLoginIdpHostLabel": { "message": "Хост провайдера идентификации" @@ -6574,7 +6617,7 @@ "message": "Мин." }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Вы действительно хотите установить максимальное ограничение по времени \"Никогда\" для всех пользователей?" }, "sessionTimeoutConfirmationNeverDescription": { "message": "Этот параметр сохранит ключи шифрования ваших пользователей на их устройствах. Если вы выберете этот параметр, убедитесь, что их устройства должным образом защищены." @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." }, + "selfHostedEnvMustUseHttps": { + "message": "URL должны использовать HTTPS." + }, "apiUrl": { "message": "URL API сервера" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Доступ запрещен. У вас нет разрешения на просмотр этой страницы." }, + "noPageAccess": { + "message": "У вас нет доступа к этой странице" + }, "masterPassword": { "message": "Мастер-пароль" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Назначить задачи" }, + "assignTasksToMembers": { + "message": "Назначайте задачи пользователям для управляемого решения" + }, "assignToCollections": { "message": "Назначить коллекциям" }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 3941850c2f2..8d40ec3d93c 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "ප්‍රියතමයන්" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "වර්ග" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 5cfc3500eb8..9dba4eb22b2 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Prehľad o prístupe" }, - "riskInsights": { - "message": "Prehľad o rizikách" - }, "passwordRisk": { "message": "Ohrozenie hesla" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Skontrolujte ohrozené heslá (slabé, odhalené, alebo opätovne použité) naprieč aplikáciami. Vyberte najkritickejšie aplikácie a priorizujte vaším používateľom bezpečnostné opatrenia ohľadom ohrozených hesiel." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Posledná aktualizácia dát: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritických aplikácií", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Neboli nájdené žiadne aplikácie v $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Pre začatie monitorovania bezpečnostných problémov importujte prihlasovacie údaje vašej organizácie. Po importe budete mať k dispozícii:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Prioritizácia bezpečnostných problémov" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Zamerajte sa na najdôležitejšie aplikácie" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Sprievodca nápravou" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Prideľte ohrozeným členom úlohy pre obnovu ohrozených prihlasovacích údajov" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Monitorovanie progresu" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Zobrazte zlepšenie zabezpečenia sledovaním zmien v priebehu času" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Pre prehľad aplikácií generujte váš prvý report" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Generujte prehľad rizík pre analýzu aplikácií vo vašej organizácii a identifikujte ohrozene heslá ktoré vyžadujú vašu pozornosť. Spustením vášho prvého reportu:" }, "noCriticalApplicationsTitle": { "message": "Neoznačili ste žiadne aplikácie ako kritické" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Aplikácie označené ako kritické" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Nepodarilo sa označiť aplikácie za kritické" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Členovia s prístupom k ohrozeným položkám kritických aplikácii" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ ohrozených členov", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Skontrolovať teraz" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Uprednostniť kritické aplikácie" }, - "atRiskItems": { - "message": "Ohrozené položky" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Funkcia označovania za kritické bude implementovaná v budúcej aktualizácii" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Hodnotenie aplikácie uložené" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Nové hodnotené aplikácie" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Chyba pri ukladaní stavu hodnotenia" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Prosím, skúste to znova" }, "unmarkAsCritical": { "message": "Zrušiť označenie za kritické" @@ -835,6 +862,9 @@ "favorites": { "message": "Obľúbené" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Typy" }, @@ -1360,7 +1390,7 @@ "message": "Použiť jednotné prihlásenie" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Vaša organizácia vyžaduje jednotné prihlasovanie." }, "welcomeBack": { "message": "Vitajte späť" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Aktualizovať prehliadač" }, - "generatingYourRiskInsights": { - "message": "Generuje sa váš prehľad o rizikách..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Generovať report" @@ -4498,7 +4528,7 @@ "message": "Pozvánka prijatá" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Pozvánka úspešne akceptovaná." }, "inviteInitAcceptedDesc": { "message": "You can now access this organization." @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Vyžaduje sa pravidlo jednej organizácie. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Ktokoľvek je členom viacerých organizácií bude mat znemožnený prístup dokiaľ neopustí ostatné organizácie." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Pravidlo jednej organizácie sa rozšíri na všetky roly. " @@ -5872,6 +5902,19 @@ "message": "Nedovoľte používateľom skryť svoju e-mailovú adresu pred príjemcami pri vytváraní alebo úpravách Sendu.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Upravená politika $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Vaše hlavné heslo nespĺňa jednu alebo viacero podmienok vašej organizácie. Ak chcete získať prístup k trezoru, musíte teraz aktualizovať svoje hlavné heslo. Pokračovaním sa odhlásite z aktuálnej relácie a budete sa musieť znova prihlásiť. Aktívne relácie na iných zariadeniach môžu zostať aktívne až jednu hodinu." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -6541,31 +6584,31 @@ "message": "Vaša organizácia aktualizovala možnosti dešifrovania. Na prístup k trezoru nastavte hlavné heslo." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Časový limit relácie" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Nastaviť maximálny časový limit relácie pre všetkých členov okrem vlastníkov." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Maximálny povolený časový limit" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Maximálny povolený časový limit je povinný." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Čas nie je platný. Zmeňte aspoň jednu hodnotu." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Akcia po vypršaní relácie" }, "immediately": { - "message": "Immediately" + "message": "Okamžite" }, "onSystemLock": { - "message": "On system lock" + "message": "Keď je systém uzamknutý" }, "onAppRestart": { - "message": "On app restart" + "message": "Po reštarte aplikácie" }, "hours": { "message": "Hodiny" @@ -6574,19 +6617,19 @@ "message": "Minúty" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Naozaj chcete povoliť maximálny časový limit \"Nikdy\" pre všetkých členov?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Táto voľba uloží šifrovacie kľúče členov na ich zariadeniach. Ak zvolíte tuto možnosť, uistite sa ze ich zariadenia sú dostatočne zabezpečené." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Dozvedieť sa viac o zabezpečení zariadení" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "Nastavenie \"Uzamknutý systém\" sa aplikuje iba na rozšírenie pre prehliadač a aplikáciu pre desktop" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Keďže mobilná a webová aplikácia nepodporuje túto predvoľbu pre maximálny povolený časový limit, bude namiesto toho použité nastavenie \"po reštarte aplikácie\"." }, "vaultTimeoutPolicyInEffect": { "message": "Zásady vašej organizácie ovplyvňujú časový limit trezoru. Maximálny povolený časový limit trezoru je $HOURS$ h a $MINUTES$ m", @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "Adresy URL musia používať HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Prístup zamietnutý. Nemáte oprávnenie na zobrazenie tejto stránky." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Hlavné heslo" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Priradiť úlohy" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Prideliť k zbierkam" }, @@ -12016,12 +12068,12 @@ "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "PSČ" }, "cardNumberLabel": { - "message": "Card number" + "message": "Číslo karty" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Začať bezplatnú skúšku pre predplatné Rodiny" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 785a43a769c..771f16e53cf 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Analiza dostopa" }, - "riskInsights": { - "message": "Vpogled v tveganja" - }, "passwordRisk": { "message": "Varnostno tveganje gesla" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Priljubljeno" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Vrste" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Glavno geslo" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 5385ec2ea19..753ea33c2c1 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Pristupi inteligenciji" }, - "riskInsights": { - "message": "Uvid u rizik" - }, "passwordRisk": { "message": "Rizik od lozinke" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Pregledaj rizične lozinke (slabe, izložene ili ponovo korišćene) u aplikacijama. Izaberi svoje najkritičnije aplikacije da bi dao prioritet bezbednosnim radnjama kako bi tvoji korisnici adresirali rizične lozinke." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Podaci su poslednji put ažurirani: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Omiljene stavke" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Tipovi" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index e05675cd49b..e17983809ae 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Приступи интелигенцији" }, - "riskInsights": { - "message": "Увид у ризик" - }, "passwordRisk": { "message": "Ризик од лозинке" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Прегледај ризичне лозинке (слабе, изложене или поново коришћене) у апликацијама. Изабери своје најкритичније апликације да би дао приоритет безбедносним радњама како би твоји корисници адресирали ризичне лозинке." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Подаци су последњи пут ажурирани: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "Критичне апликације: $COUNT$", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Апликације означене као критичне" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Означавање апликација као критичних није успело" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Чланови са приступом за угрожене ставке критичних апликација" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "Угрожени чланови: $COUNT$", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Прегледај сада" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Дајте приоритет критичним апликацијама" }, - "atRiskItems": { - "message": "Ставке под ризиком" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Означи као критичну функционалност ће бити имплементирана у будућем ажурирању" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Омиљени" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Врсте" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Ажурирајте Претраживач" }, - "generatingYourRiskInsights": { - "message": "Генерисање прегледа вашег ризика..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Покрените извештај" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Не дозволите корисницима да сакрију своју е-пошту од примаоца приликом креирања или уређивања „Send“-а.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Политика $ID$ промењена.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ваша главна лозинка не испуњава једну или више смерница ваше организације. Да бисте приступили сефу, морате одмах да ажурирате главну лозинку. Ако наставите, одјавићете се са ваше тренутне сесије, што захтева да се поново пријавите. Активне сесије на другим уређајима могу да остану активне до један сат." }, - "automaticAppLogin": { - "message": "Аутоматски пријавите кориснике за дозвољене апликације" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Обрасци за пријаву ће аутоматски бити попуњени и послати за апликације које покреће ваш провајдер идентитета." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Хост добављача идентитета" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "УРЛ АПИ Сервера" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Одбијен приступ. Немате дозволу да видите ову страницу." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Главна Лозинка" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Додели задатке" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Додели колекцијама" }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index d3861b133b6..d8a4d43946c 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Riskinsikter" - }, "passwordRisk": { "message": "Lösenordsrisk" }, + "noEditPermissions": { + "message": "Du har inte behörighet att redigera detta objekt" + }, "reviewAtRiskPasswords": { "message": "Granska risklösenord (svaga, exponerade eller återanvända) i olika applikationer. Välj ut de mest kritiska applikationerna för att prioritera säkerhetsåtgärder för användarna för att hantera risklösenord." }, + "reviewAtRiskLoginsPrompt": { + "message": "Granska inloggningar i riskzonen" + }, "dataLastUpdated": { "message": "Data senast uppdaterade: $DATE$", "placeholders": { @@ -85,13 +88,13 @@ } }, "passwordChangeProgress": { - "message": "Password change progress" + "message": "Lösenordsändringsförlopp" }, "assignMembersTasksToMonitorProgress": { - "message": "Assign members tasks to monitor progress" + "message": "Tilldela medlemmar uppgifter för att övervaka framsteg" }, "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "message": "När du har granskat applikationer och markerat dem som kritiska kan du tilldela uppgifter till medlemmar för att åtgärda riskutsatta objekt och övervaka framsteg här" }, "sendReminders": { "message": "Skicka påminnelser" @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "kritiska applikationer markerade" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritiska applikationer", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Inga applikationer hittades för $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,28 +188,28 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Importera din organisations inloggningsdata för att börja övervaka säkerhetsrisker för autentiseringsuppgifter. När de har importerats kan du:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Prioritera risker" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Fokusera på applikationer som betyder mest" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Guide åtgärder" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Tilldela riskutsatta medlemmar vägledda uppgifter för att byta ut riskutsatta autentiseringsuppgifter" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Övervaka framsteg" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Spåra förändringar över tid för att visa säkerhetsförbättringar" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Kör din första rapport för att se applikationer" }, "noReportRunDescription": { "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applikationer markerade som kritiska" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Misslyckades med att markera applikationer som kritiska" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ medlemmar i riskzonen", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Granska nu" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Inga nya applikationer att granska just nu" + }, "prioritizeCriticalApplications": { "message": "Prioritera kritiska applikationer" }, - "atRiskItems": { - "message": "Objekt i riskzonen" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Granska nya applikationer" + }, + "reviewNewApplicationsDescription": { + "message": "Vi har markerat objekt i riskzonen för nya applikationer som lagras i administratörskonsolen och som har svaga, exponerade eller återanvända lösenord." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Markera som kritisk funktionalitet kommer att implementeras i en framtida uppdatering" @@ -355,23 +391,14 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Nya applikationer granskade" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Fel vid sparande av granskningsstatus" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Försök igen" }, "unmarkAsCritical": { "message": "Avmarkera som kritisk" @@ -835,6 +862,9 @@ "favorites": { "message": "Favoriter" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Typer" }, @@ -1360,7 +1390,7 @@ "message": "Använd Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Din organisation kräver single sign-on." }, "welcomeBack": { "message": "Välkommen tillbaka" @@ -1477,7 +1507,7 @@ "message": "Tryck på din YubiKey för att autentisera" }, "authenticationTimeout": { - "message": "Timeout för autentisering" + "message": "Tidsgräns för autentisering" }, "authenticationSessionTimedOut": { "message": "Autentiseringssessionen timade ut. Vänligen starta om inloggningsprocessen." @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Uppdatera webbläsare" }, - "generatingYourRiskInsights": { - "message": "Skapa din riskinsikt..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Kör rapport" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "En organisationspolicy krävs. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5833,10 +5863,10 @@ "message": "På grund av en av företagets policyer är du begränsad från att spara objekt till ditt personliga valv. Ändra ägarskap till en organisation och välj från tillgängliga samlingar." }, "desktopAutotypePolicy": { - "message": "Desktop Autotype Default Setting" + "message": "Standardinställning för autotype" }, "desktopAutotypePolicyDesc": { - "message": "Turn Desktop Autotype ON by default for members. Members can turn Autotype off manually in the Desktop client.", + "message": "Aktivera Desktop Autotype som standard för medlemmar. Medlemmar kan stänga av Desktop Autotype manuellt i skrivbordsklienten.", "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { @@ -5872,6 +5902,19 @@ "message": "Tillåt inte användare att dölja sin e-postadress från mottagare när de skapar eller redigerar en Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Standardmatchning för URI" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Avgör när inloggningar föreslås för autofyll. Administratörer och ägare undantas från denna policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Standardmatchning för URI" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Ändrade policyn $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ditt huvudlösenord uppfyller inte en eller flera av organisationens policyer. För att få tillgång till valvet måste du uppdatera ditt huvudlösenord nu. Om du fortsätter loggas du ut från din nuvarande session och måste logga in igen. Aktiva sessioner på andra enheter kan fortsätta att vara aktiva i upp till en timme." }, - "automaticAppLogin": { - "message": "Automatiskt logga in användare för tillåtna applikationer" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Inloggningsformulär fylls i och skickas automatiskt för appar som startas från din konfigurerade identitetsleverantör." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identitetsleverantörens värd" @@ -6541,31 +6584,31 @@ "message": "Din organisation har uppdaterat dina dekrypteringsalternativ. Ange ett huvudlösenord för att komma åt ditt valv." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Tidsgräns för session" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Ställ in en maximal tidsgräns för sessioner för alla medlemmar förutom ägare." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Maximalt tillåten tidsgräns" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Maximalt tillåten tidsgräns krävs." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Tiden är ogiltig. Ändra minst ett värde." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Åtgärd vid sessions­tidsgräns" }, "immediately": { - "message": "Immediately" + "message": "Omedelbart" }, "onSystemLock": { - "message": "On system lock" + "message": "Vid låsning av datorn" }, "onAppRestart": { - "message": "On app restart" + "message": "Vid omstart av app" }, "hours": { "message": "Timmar" @@ -6574,19 +6617,19 @@ "message": "Minuter" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Är du säker på att du vill tillåta en maximal timeout av \"Aldrig\" för alla medlemmar?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Det här alternativet kommer att spara dina medlemmars krypteringsnycklar på deras enheter. Om du väljer det här alternativet, se till att deras enheter är tillräckligt skyddade." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Läs mer om enhetsskydd" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"System lock\" kommer endast att gälla för webbläsare och skrivbordsapp" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Mobil- och webbappen kommer att använda \"vid omstart av app\" som deras högsta tillåtna timeout, eftersom alternativet inte stöds." }, "vaultTimeoutPolicyInEffect": { "message": "I organisationens policyer har den maximalt tillåtna tidsgränsen för valvet angetts till $HOURS$ timme(n) och $MINUTES$ minut(er).", @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Du måste lägga till antingen basserverns URL eller minst en anpassad miljö." }, + "selfHostedEnvMustUseHttps": { + "message": "Webbadresser måste använda HTTPS." + }, "apiUrl": { "message": "URL till API-server" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Åtkomst nekad. Du har inte behörighet att visa den här sidan." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Huvudlösenord" }, @@ -7398,11 +7447,11 @@ } }, "plusAddressedEmail": { - "message": "Plus adresserad e-post", + "message": "Plusadresserad e-post", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" }, "plusAddressedEmailDesc": { - "message": "Använd din e-postleverantörs funktioner för underadressering." + "message": "Använd din e-postleverantörs funktion för subadressering." }, "catchallEmail": { "message": "E-post för alla" @@ -7453,7 +7502,7 @@ "message": "Okänd hemlighet, du kan behöver begära behörighet för att komma åt denna hemlighet." }, "unknownServiceAccount": { - "message": "Unknown machine account, you may need to request permission to access this machine account." + "message": "Okänt maskinkonto. Du kan behöva begära tillstånd för att få tillgång till detta maskinkonto." }, "unknownProject": { "message": "Okänt projekt, du kan behöver begära behörighet för att komma åt detta projekt." @@ -8877,7 +8926,7 @@ } }, "removedGroupFromServiceAccountWithId": { - "message": "Removed group: $GROUP_ID$ from machine account with identifier: $SERVICE_ACCOUNT_ID$", + "message": "Tog bort grupp $GROUP_ID$ från maskinkonto med identifierare $SERVICE_ACCOUNT_ID$", "placeholders": { "group_id": { "content": "$1", @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Tilldela uppgifter" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Tilldela till samlingar" }, @@ -10263,7 +10315,7 @@ "message": "Bärartoken" }, "repositoryNameHint": { - "message": "Name of the repository to ingest into" + "message": "Namn på arkivet som ska importeras till" }, "index": { "message": "Index" @@ -11231,13 +11283,13 @@ "message": "Vi hade problem med att öppna webbläsartillägget Bitwarden. Klicka på knappen för att öppna det nu." }, "openExtension": { - "message": "Öppen förlängning" + "message": "Öppna tillägg" }, "doNotHaveExtension": { "message": "Har du inte webbläsartillägget Bitwarden?" }, "installExtension": { - "message": "Installera förlängning" + "message": "Installera tillägg" }, "openedExtension": { "message": "Öppnade webbläsartillägget" @@ -11435,7 +11487,7 @@ "message": "Inga objekt i arkivet" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "Arkiverade objekt visas här och undantas från allmänna sökresultat och autofyllförslag." }, "itemWasSentToArchive": { "message": "Objektet skickades till arkivet" @@ -11457,14 +11509,14 @@ "description": "Verb" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "Arkiverade objekt undantas från allmänna sökresultat och autofyllförslag. Är du säker på att du vill arkivera det här objektet?" }, "archiveBulkItems": { "message": "Arkivera objekt", "description": "Verb" }, "archiveBulkItemsConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive these items?" + "message": "Arkiverade objekt undantas från allmänna sökresultat och autofyllförslag. Är du säker på att du vill arkivera dessa objekt?" }, "businessUnit": { "message": "Affärsenhet" @@ -11571,7 +11623,7 @@ "message": "Hoppa till webbapp" }, "bitwardenExtensionInstalled": { - "message": "Bitvärdesförlängningen installerad!" + "message": "Bitwarden-tillägg installerat!" }, "openTheBitwardenExtension": { "message": "Öppna Bitwarden-tillägget" @@ -11583,7 +11635,7 @@ "message": "Öppna tillägget för att logga in och starta autofyllning." }, "openBitwardenExtension": { - "message": "Öppna Bitwarden-förlängning" + "message": "Öppna Bitwarden-tillägget" }, "gettingStartedWithBitwardenPart1": { "message": "För tips om hur du kommer igång med Bitwarden besök", @@ -11812,22 +11864,22 @@ "message": "Ytterligare maskinkonton" }, "secretsManagerSeats": { - "message": "Secrets Manager seats" + "message": "Secrets Manager-licenser" }, "additionalStorage": { "message": "Ytterligare lagring" }, "expandPurchaseDetails": { - "message": "Expand purchase details" + "message": "Visa köpuppgifter" }, "collapsePurchaseDetails": { - "message": "Collapse purchase details" + "message": "Dölj köpuppgifter" }, "familiesMembership": { "message": "Familjemedlemskap" }, "planDescPremium": { - "message": "Complete online security" + "message": "Komplett säkerhet online" }, "planDescFamiliesV2": { "message": "Premiumsäkerhet för din familj" @@ -11923,7 +11975,7 @@ "message": "Säkerhetspolicyer för företag" }, "selfHostOption": { - "message": "Self-host option" + "message": "Alternativ för self-hosting" }, "complimentaryFamiliesPlan": { "message": "Kostnadsfri familjeplan för alla användare" @@ -11950,13 +12002,13 @@ "message": "Du har uppgraderat till Familjer!" }, "taxCalculationError": { - "message": "There was an error calculating tax for your location. Please try again." + "message": "Ett fel uppstod vid beräkning av skatt för din plats. Försök igen." }, "individualUpgradeWelcomeMessage": { "message": "Välkommen till Bitwarden" }, "individualUpgradeDescriptionMessage": { - "message": "Unlock more security features with Premium, or start sharing items with Families" + "message": "Lås upp fler säkerhetsfunktioner med Premium, eller börja dela objekt med Families" }, "individualUpgradeTaxInformationMessage": { "message": "Priser exklusive moms och faktureras årligen." @@ -12022,6 +12074,6 @@ "message": "Kortnummer" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Starta gratis testperiod för Families" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 86d94a3c2c1..a5acacede66 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "அணுகல் நுண்ணறிவு" }, - "riskInsights": { - "message": "ஆபத்து நுண்ணறிவு" - }, "passwordRisk": { "message": "கடவுச்சொல் ஆபத்து" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "பயன்பாடுகள் முழுவதும் ஆபத்தான கடவுச்சொற்களை (பலவீனமான, அம்பலப்படுத்தப்பட்ட, அல்லது மீண்டும் பயன்படுத்தப்பட்ட) மதிப்பாய்வு செய்யவும். உங்கள் பயனர்களுக்கான ஆபத்தான கடவுச்சொற்களை நிவர்த்தி செய்ய பாதுகாப்பு நடவடிக்கைகளுக்கு முன்னுரிமை அளிக்க, உங்கள் மிக முக்கியமான பயன்பாடுகளைத் தேர்ந்தெடுக்கவும்." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "கடைசியாகத் தரவு புதுப்பிக்கப்பட்டது: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "பயன்பாடுகள் முக்கியமானதாகக் குறியிடப்பட்டன" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "பிடித்தவை" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "வகைகள்" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "உலாவியைப் புதுப்பிக்கவும்" }, - "generatingYourRiskInsights": { - "message": "உங்கள் இடர் நுண்ணறிவுகளை உருவாக்குகிறது..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "ஒரு Send-ஐ உருவாக்கும்போது அல்லது திருத்தும்போது பெறுநர்களுடன் உறுப்பினரின் மின்னஞ்சல் முகவரியை எப்போதும் காட்டவும்.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ கொள்கை மாற்றியமைக்கப்பட்டது.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "உங்கள் மாஸ்டர் கடவுச்சொல் ஒன்று அல்லது அதற்கு மேற்பட்ட உங்கள் நிறுவன கொள்கைகளைப் பூர்த்தி செய்யவில்லை. பெட்டகத்தை அணுக, நீங்கள் இப்போது உங்கள் மாஸ்டர் கடவுச்சொல்லைப் புதுப்பிக்க வேண்டும். தொடர்வது உங்கள் தற்போதைய அமர்விலிருந்து உங்களை வெளியேற்றும், மேலும் நீங்கள் மீண்டும் உள்நுழைய வேண்டும். பிற சாதனங்களில் உள்ள செயலில் உள்ள அமர்வுகள் ஒரு மணி நேரம் வரை செயலில் இருக்கலாம்." }, - "automaticAppLogin": { - "message": "அனுமதிக்கப்பட்ட பயன்பாடுகளுக்குப் பயனர்களைத் தானாகவே உள்நுழை" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "உங்கள் கட்டமைக்கப்பட்ட அடையாள வழங்குநரிடமிருந்து தொடங்கப்பட்ட பயன்பாடுகளுக்கு உள்நுழைவு படிவங்கள் தானாகவே நிரப்பப்பட்டு சமர்ப்பிக்கப்படும்." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "அடையாள வழங்குநர் ஹோஸ்ட்" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "நீங்கள் அடிப்படை சேவையக URL அல்லது குறைந்தபட்சம் ஒரு தனிப்பயன் சூழலையாவது சேர்க்க வேண்டும்." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API சேவையக URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "அணுகல் மறுக்கப்பட்டது. இந்தப் பக்கத்தைப் பார்க்க உங்களுக்கு அனுமதி இல்லை." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "மாஸ்டர் கடவுச்சொல்" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "சேகரிப்புகளுக்கு ஒதுக்கு" }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 338f5c10d8a..e7051dee661 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Favorites" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Types" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index ddf7d93fa23..fd74c2d12a8 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk Insights" - }, "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Data last updated: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Applications marked as critical" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "รายการโปรด" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "ชนิด" }, @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Update browser" }, - "generatingYourRiskInsights": { - "message": "Generating your Risk Insights..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Always show member’s email address with recipients when creating or editing a Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Modified policy $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour." }, - "automaticAppLogin": { - "message": "Automatically log in users for allowed applications" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Login forms will automatically be filled and submitted for apps launched from your configured identity provider." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "API server URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Access denied. You do not have permission to view this page." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Master password" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Assign to collections" }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index e019e106dc2..dc64a6a0d8c 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "Risk İçgörüleri" - }, "passwordRisk": { "message": "Parola Riski" }, + "noEditPermissions": { + "message": "Bu kaydı düzenleme yetkisine sahip değilsiniz" + }, "reviewAtRiskPasswords": { "message": "Uygulamalar genelinde risk altındaki parolaları (zayıf, açığa çıkmış veya farklı yerlerde kullanılan) gözden geçirin. Kullanıcılarınız için risk altındaki parolalara yönelik güvenlik eylemlerine öncelik vermek üzere en kritik uygulamalarınızı seçin." }, + "reviewAtRiskLoginsPrompt": { + "message": "Risk altındaki hesapları inceleyin" + }, "dataLastUpdated": { "message": "Son veri güncellemesi: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ kritik uygulama", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "$ORG NAME$ kuruluşu için hiçbir uygulama bulunmadı", "placeholders": { "org name": { "content": "$1", @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Kritik olarak işaretlenmiş uygulamalar" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Kritik uygulamalar için risk altındaki kayıtlara erişimi olan üyeler" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ üye risk altında", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "Şimdilik bu kadar!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -371,7 +398,7 @@ "message": "Error saving review status" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Lütfen yeniden deneyin" }, "unmarkAsCritical": { "message": "Kritik olarak işaretlemeyi kaldır" @@ -835,6 +862,9 @@ "favorites": { "message": "Favoriler" }, + "taskSummary": { + "message": "Görev özeti" + }, "types": { "message": "Türler" }, @@ -1360,7 +1390,7 @@ "message": "Çoklu oturum açma kullan" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Kuruluşunuz çoklu oturum açma gerektiriyor." }, "welcomeBack": { "message": "Tekrar hoş geldiniz" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Tarayıcıyı güncelle" }, - "generatingYourRiskInsights": { - "message": "Risk içgörüleriniz oluşturuluyor..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Send oluştururken veya düzenlerken üyelerin e-posta adreslerini her zaman alıcılara göster.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Varsayılan URI eşleşme tespiti" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Varsayılan URI eşleşme tespiti" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "$ID$ ilkesi düzenlendi.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ana parolanız kuruluş ilkelerinizi karşılamıyor. Kasanıza erişmek için ana parolanızı güncellemelisiniz. Devam ettiğinizde oturumunuz kapanacak ve yeniden oturum açmanız gerekecektir. Diğer cihazlardaki aktif oturumlar bir saate kadar aktif kalabilir." }, - "automaticAppLogin": { - "message": "İzin verilen uygulamalar için kullanıcıların otomatik olarak oturum açması" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Hesap formları, yapılandırılmış kimlik sağlayıcınızdan başlatılan uygulamalar için otomatik olarak doldurulacak ve gönderilecektir." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Kimlik sağlayıcı ana bilgisayarı" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Temel Sunucu URL’sini veya en az bir özel ortam eklemelisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL'ler HTTPS kullanmalıdır." + }, "apiUrl": { "message": "API sunucusu URL'si" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Erişim engellendi. Bu sayfayı görüntüleme iznine sahip değilsiniz." }, + "noPageAccess": { + "message": "Bu sayfaya erişiminiz yok" + }, "masterPassword": { "message": "Ana parola" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Koleksiyonlara ata" }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index d763cfb3203..e1a00b3dde4 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Управління доступом" }, - "riskInsights": { - "message": "Інформація щодо ризику" - }, "passwordRisk": { "message": "Ризиковані паролі" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Переглядайте ризиковані паролі в різних програмах (слабкі, викриті, або повторно використані). Виберіть найбільш критичні програми, щоб визначити пріоритети дій щодо безпеки для користувачів, які використовують ризиковані паролі." }, + "reviewAtRiskLoginsPrompt": { + "message": "Review at-risk logins" + }, "dataLastUpdated": { "message": "Дані востаннє оновлено: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "critical applications marked" + }, "countOfCriticalApplications": { "message": "$COUNT$ critical applications", "placeholders": { @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Позначені критичні програми" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ applications marked as critical", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Failed to mark applications as critical" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Members with access to at-risk items for critical applications" }, + "membersWithAtRiskPasswords": { + "message": "Members with at-risk passwords" + }, + "membersWillReceiveNotification": { + "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", "placeholders": { @@ -343,11 +364,26 @@ "reviewNow": { "message": "Review now" }, + "allCaughtUp": { + "message": "All caught up!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "No new applications to review at this time" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "atRiskItems": { - "message": "At-risk items" + "selectCriticalApplicationsDescription": { + "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Click the star icon to mark an app as critical" }, "markAsCriticalPlaceholder": { "message": "Mark as critical functionality will be implemented in a future update" @@ -355,15 +391,6 @@ "applicationReviewSaved": { "message": "Application review saved" }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } - }, "newApplicationsReviewed": { "message": "New applications reviewed" }, @@ -835,6 +862,9 @@ "favorites": { "message": "Обране" }, + "taskSummary": { + "message": "Task summary" + }, "types": { "message": "Типи" }, @@ -2495,23 +2525,23 @@ "message": "Відновити доступ" }, "premium": { - "message": "Преміум", + "message": "Premium", "description": "Premium membership" }, "premiumMembership": { - "message": "Преміум статус" + "message": "Передплата Premium" }, "premiumRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "premiumRequiredDesc": { - "message": "Для використання цієї функції необхідна передплата преміум." + "message": "Для використання цієї функції необхідна передплата Premium." }, "youHavePremiumAccess": { - "message": "У вас є преміумдоступ" + "message": "У вас є Premium" }, "alreadyPremiumFromOrg": { - "message": "У вас вже є доступ до преміумфункцій, тому що ви входите до організації, яка вам їх надає." + "message": "У вас вже є доступ до функцій Premium, тому що ви входите до організації, яка вам їх надає." }, "manage": { "message": "Керувати" @@ -3036,10 +3066,10 @@ "message": "Пріоритетну технічну підтримку." }, "premiumSignUpFuture": { - "message": "Усі майбутні преміумфункції. Їх буде більше!" + "message": "Усі майбутні функції Premium. Їх буде більше!" }, "premiumPrice": { - "message": "Всього лише $PRICE$ / за рік!", + "message": "Лише $PRICE$ / рік!", "placeholders": { "price": { "content": "$1", @@ -3048,7 +3078,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "Отримайте преміум – лише $PRICE$ /рік. Або отримайте преміум обліковий запис для користувачів $FAMILYPLANUSERCOUNT$ та необмежений спільний доступ з ", + "message": "Отримайте Premium – лише $PRICE$ /рік. Або отримайте обліковий запис Premium для користувачів $FAMILYPLANUSERCOUNT$ та необмежений спільний доступ з ", "placeholders": { "price": { "content": "$1", @@ -3326,7 +3356,7 @@ "message": "Спосіб оплати оновлено." }, "purchasePremium": { - "message": "Придбати преміум" + "message": "Придбати Premium" }, "licenseFile": { "message": "Файл ліцензії" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Оновити браузер" }, - "generatingYourRiskInsights": { - "message": "Генерується інформація щодо ризиків..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Run report" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Single organization policy required. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Anyone part of more than one organization will have their access revoked until they leave the other organizations." + "autoConfirmSingleOrgRequiredDesc": { + "message": "All members must only belong to this organization to activate this automation." }, "autoConfirmSingleOrgExemption": { "message": "Single organization policy will extend to all roles. " @@ -5872,6 +5902,19 @@ "message": "Завжди показувати отримувачам адресу е-пошти учасників під час створення чи редагування відправлень.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Default URI match detection" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Default URI match detection" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Please select a valid URI match detection option.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Змінено політику $ID$.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Ваш головний пароль не відповідає одній або більше політикам вашої організації. Щоб отримати доступ до сховища, вам необхідно оновити свій головний пароль зараз. Продовживши, ви вийдете з поточного сеансу, після чого потрібно буде повторно виконати вхід. Сеанси на інших пристроях можуть залишатися активними протягом однієї години." }, - "automaticAppLogin": { - "message": "Автоматичний вхід користувачів для дозволених програм" + "automaticAppLoginWithSSO": { + "message": "Automatic login with SSO" }, - "automaticAppLoginDesc": { - "message": "Форми входу автоматично заповнюватимуться і надсилатимуться для програм, запущених від налаштованого провайдера ідентифікації." + "automaticAppLoginWithSSODesc": { + "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." }, "automaticAppLoginIdpHostLabel": { "message": "Вузол провайдера ідентифікації" @@ -6821,7 +6864,7 @@ "message": "План Bitwarden Families включає" }, "sponsoredFamiliesPremiumAccess": { - "message": "Преміумдоступ до 6 користувачів" + "message": "Доступ Premium для 6 користувачів" }, "sponsoredFamiliesSharedCollectionsForFamilyMembers": { "message": "Спільні збірки для учасників родини" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "apiUrl": { "message": "URL-адреса сервера API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Доступ заборонено. У вас немає дозволу на перегляд цієї сторінки." }, + "noPageAccess": { + "message": "You do not have access to this page" + }, "masterPassword": { "message": "Головний пароль" }, @@ -7674,7 +7723,7 @@ } }, "premiumSubcriptionRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "scim": { "message": "Розгортання SCIM", @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Assign tasks" }, + "assignTasksToMembers": { + "message": "Assign tasks to members for guided resolution" + }, "assignToCollections": { "message": "Призначити до збірок" }, @@ -10777,7 +10829,7 @@ "message": "Захистіть паролі своєї родини" }, "accessToPremiumFeatures": { - "message": "Доступ до преміальних функцій" + "message": "Доступ до функцій Premium" }, "additionalStorageGbMessage": { "message": "ГБ додаткового сховища" @@ -10816,7 +10868,7 @@ "message": "RSA 4096-Bit" }, "premiumAccounts": { - "message": "6 облікових записів Преміум" + "message": "6 облікових записів Premium" }, "unlimitedSharing": { "message": "Необмежений спільний доступ" @@ -11827,10 +11879,10 @@ "message": "Families membership" }, "planDescPremium": { - "message": "Complete online security" + "message": "Повна онлайн-безпека" }, "planDescFamiliesV2": { - "message": "Premium security for your family" + "message": "Безпека Premium для вашої сім'ї" }, "planDescFreeV2": { "message": "Share with $COUNT$ other user", @@ -11944,7 +11996,7 @@ "message": "Upgrade to Families" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Покращити до Premium" }, "familiesUpdated": { "message": "You've upgraded to Families!" @@ -11956,7 +12008,7 @@ "message": "Welcome to Bitwarden" }, "individualUpgradeDescriptionMessage": { - "message": "Unlock more security features with Premium, or start sharing items with Families" + "message": "Розблокуйте більше можливостей безпеки з передплатою Premium, або почніть ділитися записами з родиною" }, "individualUpgradeTaxInformationMessage": { "message": "Prices exclude tax and are billed annually." diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 4715652dbf6..08df68b6d33 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Trí tuệ truy cập" }, - "riskInsights": { - "message": "Thấu hiểu về rủi ro" - }, "passwordRisk": { "message": "Mật khẩu rủi ro" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Kiểm tra các mật khẩu có rủi ro (yếu, bị lộ hoặc được sử dụng lại) trên các ứng dụng. Chọn các ứng dụng quan trọng nhất của bạn để ưu tiên các biện pháp bảo mật cho người dùng nhằm giải quyết các mật khẩu có rủi ro." }, + "reviewAtRiskLoginsPrompt": { + "message": "Xem lại các đăng nhập có rủi ro" + }, "dataLastUpdated": { "message": "Dữ liệu được cập nhật lần cuối: $DATE$", "placeholders": { @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "ứng dụng quan trọng đã được đánh dấu" + }, "countOfCriticalApplications": { "message": "$COUNT$ ứng dụng quan trọng", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Không tìm thấy ứng dụng nào cho $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Nhập dữ liệu đăng nhập của tổ chức bạn để bắt đầu giám sát các rủi ro bảo mật thông tin xác thực. Sau khi nhập, bạn có thể:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Ưu tiên các rủi ro" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Tập trung vào các ứng dụng quan trọng nhất" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Hướng dẫn khắc phục" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Giao nhiệm vụ được hướng dẫn cho các thành viên có rủi ro để xoay vòng thông tin xác thực có rủi ro" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Theo dõi tiến độ" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Theo dõi thay đổi theo thời gian để hiển thị cải tiến bảo mật" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Chạy báo cáo đầu tiên của bạn để xem các ứng dụng" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Tạo báo cáo thông tin chi tiết về rủi ro để phân tích các ứng dụng của tổ chức bạn và xác định các mật khẩu có rủi ro cần chú ý. Khi chạy báo cáo đầu tiên, bạn sẽ:" }, "noCriticalApplicationsTitle": { "message": "Bạn chưa đánh dấu ứng dụng nào là quan trọng" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "Các ứng dụng được đánh dấu là quan trọng" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ ứng dụng được đánh dấu là quan trọng", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "Không thể đánh dấu các ứng dụng là quan trọng" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "Thành viên có quyền truy cập vào các mục có nguy cơ dành cho các ứng dụng quan trọng" }, + "membersWithAtRiskPasswords": { + "message": "Thành viên có mật khẩu rủi ro" + }, + "membersWillReceiveNotification": { + "message": "Các thành viên sẽ nhận được thông báo để giải quyết các đăng nhập có rủi ro thông qua tiện ích mở rộng trình duyệt." + }, "membersAtRiskCount": { "message": "$COUNT$ thành viên gặp rủi ro", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "Đánh giá ngay" }, + "allCaughtUp": { + "message": "Tất cả đã hoàn tất!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "Hiện tại không có ứng dụng mới nào để đánh giá" + }, "prioritizeCriticalApplications": { "message": "Ưu tiên các ứng dụng quan trọng" }, - "atRiskItems": { - "message": "Các mục có nguy cơ" + "selectCriticalApplicationsDescription": { + "message": "Chọn những ứng dụng quan trọng nhất đối với tổ chức của bạn, sau đó giao nhiệm vụ bảo mật cho các thành viên để giải quyết rủi ro." + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "Nhấp vào biểu tượng ngôi sao để đánh dấu một ứng dụng là quan trọng" }, "markAsCriticalPlaceholder": { "message": "Chức năng đánh dấu là quan trọng sẽ được triển khai trong bản cập nhật sau" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "Đã lưu đánh giá ứng dụng" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Các ứng dụng mới đã được đánh giá" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Lỗi lưu trạng thái đánh giá" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Vui lòng thử lại" }, "unmarkAsCritical": { "message": "Bỏ đánh dấu là nghiêm trọng" @@ -835,6 +862,9 @@ "favorites": { "message": "Yêu thích" }, + "taskSummary": { + "message": "Tóm tắt nhiệm vụ" + }, "types": { "message": "Loại" }, @@ -1360,7 +1390,7 @@ "message": "Dùng đăng nhập một lần" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tổ chức của bạn yêu cầu đăng nhập một lần." }, "welcomeBack": { "message": "Chào mừng bạn trở lại" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "Cập nhật trình duyệt" }, - "generatingYourRiskInsights": { - "message": "Đang tạo báo cáo phân tích rủi ro của bạn..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "Chạy báo cáo" @@ -4498,7 +4528,7 @@ "message": "Đã chấp nhận lời mời" }, "invitationAcceptedDesc": { - "message": "Successfully accepted your invitation." + "message": "Đã chấp nhận lời mời của bạn thành công." }, "inviteInitAcceptedDesc": { "message": "Bạn hiện có thể truy cập tổ chức này." @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "Chính sách một tổ chức là bắt buộc. " }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "Bất kỳ ai thuộc nhiều hơn một tổ chức sẽ bị thu hồi quyền truy cập cho đến khi họ rời khỏi các tổ chức khác." + "autoConfirmSingleOrgRequiredDesc": { + "message": "Tất cả thành viên chỉ phải thuộc tổ chức này để kích hoạt tự động hóa này." }, "autoConfirmSingleOrgExemption": { "message": "Chính sách một tổ chức sẽ áp dụng cho tất cả các vai trò. " @@ -5872,6 +5902,19 @@ "message": "Luôn hiển thị địa chỉ email của thành viên cho người nhận khi tạo hoặc chỉnh sửa Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "Phát hiện khớp URI mặc định" + }, + "uriMatchDetectionPolicyDesc": { + "message": "Xác định thời điểm nên đề xuất tự động điền thông tin đăng nhập. Quản trị viên và chủ sở hữu không bị ràng buộc bởi chính sách này." + }, + "uriMatchDetectionOptionsLabel": { + "message": "Phát hiện khớp URI mặc định" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "Vui lòng chọn một tùy chọn phát hiện khớp URI hợp lệ.", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "Chính sách $ID$ đã được điều chỉnh.", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "Mật khẩu chính của bạn không đáp ứng một hoặc nhiều chính sách của tổ chức. Để truy cập kho, bạn phải cập nhật mật khẩu chính ngay bây giờ. Việc tiếp tục sẽ đăng xuất phiên hiện tại của bạn và yêu cầu bạn đăng nhập lại. Các phiên hoạt động trên các thiết bị khác có thể vẫn duy trì trong tối đa một giờ." }, - "automaticAppLogin": { - "message": "Tự động đăng nhập người dùng cho các ứng dụng được phép" + "automaticAppLoginWithSSO": { + "message": "Tự động đăng nhập bằng SSO" }, - "automaticAppLoginDesc": { - "message": "Các biểu mẫu đăng nhập sẽ tự động được điền và gửi cho các ứng dụng khởi chạy từ nhà cung cấp định danh mà bạn đã cấu hình." + "automaticAppLoginWithSSODesc": { + "message": "Mở rộng tính năng đăng nhập một lần (SSO) để bảo mật và tiện lợi hơn cho các ứng dụng ngoài hệ thống. Khi người dùng mở một ứng dụng từ nhà cung cấp danh tính của bạn, thông tin đăng nhập sẽ được tự động điền và gửi đi, giúp họ truy cập an toàn chỉ với một lần nhấp." }, "automaticAppLoginIdpHostLabel": { "message": "Máy chủ nhà cung cấp định danh" @@ -6541,31 +6584,31 @@ "message": "Tổ chức của bạn đã cập nhật tùy chọn giải mã. Vui lòng đặt mật khẩu chính để truy cập vào kho lưu trữ của bạn." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Thời gian hết phiên" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Đặt thời gian chờ phiên tối đa cho tất cả thành viên ngoại trừ chủ sở hữu." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Thời gian chờ tối đa được phép" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Yêu cầu thời gian chờ tối đa được phép." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Thời gian không hợp lệ. Hãy thay đổi ít nhất một giá trị." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Hành động khi hết thời gian chờ phiên" }, "immediately": { - "message": "Immediately" + "message": "Ngay lập tức" }, "onSystemLock": { - "message": "On system lock" + "message": "Khi khóa hệ thống" }, "onAppRestart": { - "message": "On app restart" + "message": "Khi khởi động lại ứng dụng" }, "hours": { "message": "Giờ" @@ -6574,19 +6617,19 @@ "message": "Phút" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Bạn có chắc chắn muốn cho phép thời gian chờ tối đa là \"Không bao giờ\" cho tất cả thành viên không?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Tùy chọn này sẽ lưu các khóa mã hóa của thành viên trên thiết bị của họ. Nếu bạn chọn tùy chọn này, hãy đảm bảo rằng thiết bị của họ được bảo vệ đầy đủ." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Tìm hiểu thêm về bảo vệ thiết bị" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "\"Khóa hệ thống\" sẽ chỉ áp dụng cho trình duyệt và ứng dụng máy tính." }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Ứng dụng di động và ứng dụng web sẽ đặt giới hạn thời gian chờ tối đa là “khi khởi động lại ứng dụng”, vì tùy chọn này không được hỗ trợ." }, "vaultTimeoutPolicyInEffect": { "message": "Chính sách của tổ chức bạn đã đặt thời gian chờ tối đa của kho là $HOURS$ giờ và $MINUTES$ phút.", @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "Bạn phải thêm URL Máy chủ gốc hoặc ít nhất một môi trường tùy chỉnh." }, + "selfHostedEnvMustUseHttps": { + "message": "URL phải sử dụng HTTPS." + }, "apiUrl": { "message": "URL máy chủ API" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "Truy cập bị từ chối. Bạn không có quyền xem trang này." }, + "noPageAccess": { + "message": "Bạn không có quyền truy cập vào trang này" + }, "masterPassword": { "message": "Mật khẩu chính" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "Giao tác vụ" }, + "assignTasksToMembers": { + "message": "Giao nhiệm vụ cho các thành viên để hướng dẫn giải quyết" + }, "assignToCollections": { "message": "Gán vào bộ sưu tập" }, @@ -12016,12 +12068,12 @@ "message": "Argon2id mang đến khả năng bảo vệ mạnh hơn trước các hình thức tấn công hiện đại, phù hợp nhất cho người dùng nâng cao với thiết bị mạnh mẽ." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Mã ZIP / Bưu điện" }, "cardNumberLabel": { "message": "Số thẻ" }, "startFreeFamiliesTrial": { - "message": "Start free Families trial" + "message": "Bắt đầu dùng thử Gói Gia đình miễn phí" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 80833c17225..c8d46b34254 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "Access Intelligence" }, - "riskInsights": { - "message": "风险洞察" - }, "passwordRisk": { "message": "密码风险" }, + "noEditPermissions": { + "message": "您没有编辑此项目的权限" + }, "reviewAtRiskPasswords": { "message": "审查各个应用程序中存在风险的密码(弱、暴露或重复使用)。选择最关键的应用程序,优先为您的用户采取安全措施,以处理存在风险的密码。" }, + "reviewAtRiskLoginsPrompt": { + "message": "审查存在风险的登录" + }, "dataLastUpdated": { "message": "数据最后更新于:$DATE$", "placeholders": { @@ -72,7 +75,7 @@ } }, "securityTasksCompleted": { - "message": "总计 $TOTAL$ 个中的 $COUNT$ 个安全任务已完成", + "message": "总计 $TOTAL$ 个安全任务中的 $COUNT$ 个已完成", "placeholders": { "count": { "content": "$1", @@ -106,7 +109,7 @@ "message": "查看存在风险的应用程序" }, "criticalApplicationsAreAtRisk": { - "message": "总计 $TOTAL$ 个中的 $COUNT$ 个关键应用程序因密码风险而存在风险", + "message": "总计 $TOTAL$ 个关键应用程序中的 $COUNT$ 个因密码风险而存在风险", "placeholders": { "count": { "content": "$1", @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "关键应用程序已标记" + }, "countOfCriticalApplications": { "message": "$COUNT$ 个关键应用程序", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "未找到与 $ORG NAME$ 相关的应用程序", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "导入您组织的登录数据,以开始监控凭据安全风险。导入后您将能够:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "优先处理风险" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "重点关注最重要的应用程序" }, "benefit2Title": { - "message": "Guide remediation" + "message": "指导补救措施" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "为存在风险的成员分配引导式任务,以轮换存在风险的凭据" }, "benefit3Title": { - "message": "Monitor progress" + "message": "监控进度" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "追踪变化趋势,展示安全改进成效" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "运行您的第一份报告以查看应用程序" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "生成风险洞察报告,以分析您组织的应用程序并识别需要关注的风险密码。运行您的第一份报告将:" }, "noCriticalApplicationsTitle": { "message": "您还没有将任何应用程序标记为关键" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "标记为关键的应用程序" }, + "criticalApplicationsMarkedSuccess": { + "message": "$COUNT$ 个应用程序标记为关键", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "无法将应用程序标记为关键" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "对关键应用程序中存在风险的项目具有访问权限的成员" }, + "membersWithAtRiskPasswords": { + "message": "密码存在风险的成员" + }, + "membersWillReceiveNotification": { + "message": "成员将通过浏览器扩展收到通知,以解决存在风险的登录。" + }, "membersAtRiskCount": { "message": "$COUNT$ 个成员存在风险", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "立即审查" }, - "prioritizeCriticalApplications": { - "message": "关键应用程序优先" + "allCaughtUp": { + "message": "全部处理完成!" }, - "atRiskItems": { - "message": "存在风险的项目" + "noNewApplicationsToReviewAtThisTime": { + "message": "目前没有新应用程序需要审查" + }, + "prioritizeCriticalApplications": { + "message": "优先处理关键应用程序" + }, + "selectCriticalApplicationsDescription": { + "message": "选择对您的组织最关键的应用程序,然后将安全任务分配给成员以解决风险。" + }, + "reviewNewApplications": { + "message": "审查新应用程序" + }, + "reviewNewApplicationsDescription": { + "message": "我们突出显示了管理控制台中存储的新应用程序中存在风险的项目,这些项目使用了弱、暴露或重复使用的密码。" + }, + "clickIconToMarkAppAsCritical": { + "message": "点击星形图标以将 App 标记为关键" }, "markAsCriticalPlaceholder": { "message": "标记为关键功能将在未来更新中实现" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "应用程序审查已保存" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "新应用程序已审查" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "保存审查状态时出错" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "请重试" }, "unmarkAsCritical": { "message": "取消标记为关键" @@ -484,7 +511,7 @@ "message": "删除网站" }, "defaultLabel": { - "message": "默认 ($VALUE$)", + "message": "默认($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -835,6 +862,9 @@ "favorites": { "message": "收藏夹" }, + "taskSummary": { + "message": "任务摘要" + }, "types": { "message": "类型" }, @@ -1360,7 +1390,7 @@ "message": "使用单点登录" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的组织要求单点登录。" }, "welcomeBack": { "message": "欢迎回来" @@ -3012,7 +3042,7 @@ "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { - "message": "您已升级到高级会员。" + "message": "您已升级为高级版。" }, "premiumUpgradeUnlockFeatures": { "message": "将您的账户升级为高级会员,将解锁一些强大的附加功能。" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "更新浏览器" }, - "generatingYourRiskInsights": { - "message": "正在生成风险洞察..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "运行报告" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "要求单一组织策略。" }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "属于多个组织的任何人,他们的访问权限将被撤销,直到他们离开其他组织。" + "autoConfirmSingleOrgRequiredDesc": { + "message": "所有成员必须仅属于此组织才能激活此自动化功能。" }, "autoConfirmSingleOrgExemption": { "message": "单一组织策略将扩展到所有角色。" @@ -5872,6 +5902,19 @@ "message": "创建或编辑 Send 时,始终向接收者显示成员的电子邮箱地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "默认 URI 匹配检测" + }, + "uriMatchDetectionPolicyDesc": { + "message": "确定何时建议登录用于自动填充。管理员和所有者不受此策略的约束。" + }, + "uriMatchDetectionOptionsLabel": { + "message": "默认 URI 匹配检测" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "请选择一个有效的 URI 匹配检测选项。", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "修改了策略 $ID$。", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "您的主密码不符合某一项或多项组织策略要求。要访问密码库,必须立即更新您的主密码。继续操作将使您退出当前会话,并要求您重新登录。其他设备上的活动会话可能会继续保持活动状态长达一小时。" }, - "automaticAppLogin": { - "message": "为允许的应用程序自动登录用户" + "automaticAppLoginWithSSO": { + "message": "使用 SSO 自动登录" }, - "automaticAppLoginDesc": { - "message": "从您配置的身份提供程序启动的 App 的登录表单将自动填充并提交。" + "automaticAppLoginWithSSODesc": { + "message": "将 SSO 的安全性和便捷性扩展到非托管 App。当用户通过身份提供程序启动 App 时,他们的登录信息将自动填写并提交,从而实现从身份提供程序到 App 的一键式安全流程。" }, "automaticAppLoginIdpHostLabel": { "message": "身份提供程序主机" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必须使用 HTTPS。" + }, "apiUrl": { "message": "API 服务器 URL" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "访问被拒绝。您没有权限查看此页面。" }, + "noPageAccess": { + "message": "您没有访问该页面的权限" + }, "masterPassword": { "message": "主密码" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "分配任务" }, + "assignTasksToMembers": { + "message": "将任务分配给成员以引导式解决" + }, "assignToCollections": { "message": "分配到集合" }, diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 2f2e0e80c09..4b6f0725cea 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -17,15 +17,18 @@ "accessIntelligence": { "message": "存取資訊" }, - "riskInsights": { - "message": "風險洞察" - }, "passwordRisk": { "message": "密碼風險" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "檢視全部應用中具有風險的密碼 (弱、被暴露或重複使用)。選擇最重要的應用程式並優先採取安全措施,幫助使用者解決具有風險的密碼。" }, + "reviewAtRiskLoginsPrompt": { + "message": "檢視有風險的登入資訊" + }, "dataLastUpdated": { "message": "上次資料更新日期:$DATE$", "placeholders": { @@ -94,7 +97,7 @@ "message": "在您檢視應用程式並將其標記為關鍵後,您可以指派任務給成員,以解決有風險的項目,並在此監控進度" }, "sendReminders": { - "message": "發送提醒" + "message": "傳送提醒" }, "onceYouMarkApplicationsCriticalTheyWillDisplayHere": { "message": "一旦您標記應用程式為關鍵,它們將顯示在此。" @@ -127,6 +130,9 @@ } } }, + "criticalApplicationsMarked": { + "message": "已將應用程式標記為關鍵" + }, "countOfCriticalApplications": { "message": "$COUNT$ 個關鍵應用程式", "placeholders": { @@ -173,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "未找到 $ORG NAME$ 的應用程式", "placeholders": { "org name": { "content": "$1", @@ -182,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "匯入您組織的登入資料以開始監控憑證安全風險。匯入後您可以:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "風險優先排序" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "專注於最重要的應用程式" }, "benefit2Title": { - "message": "Guide remediation" + "message": "指導補救措施" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "指派有風險的成員執行指導任務以輪換有風險的憑證" }, "benefit3Title": { - "message": "Monitor progress" + "message": "監控進展" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "追蹤隨時間變化的狀況以顯示安全性改善" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "執行您的第一份報告以查看應用程式" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "產生風險洞察報告以分析組織的應用程式並找出需要注意的高風險密碼。執行第一份報告將會:" }, "noCriticalApplicationsTitle": { "message": "您尚未將任何應用程式標記為關鍵" @@ -235,6 +241,15 @@ "applicationsMarkedAsCriticalSuccess": { "message": "被標註重要的應用程式" }, + "criticalApplicationsMarkedSuccess": { + "message": "已將 $COUNT$ 個應用程式標記為關鍵", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "applicationsMarkedAsCriticalFail": { "message": "標記應用程式為關鍵失敗" }, @@ -259,6 +274,12 @@ "membersWithAccessToAtRiskItemsForCriticalApps": { "message": "擁有關鍵應用程式中有風險項目存取權的成員" }, + "membersWithAtRiskPasswords": { + "message": "使用有風險密碼的成員" + }, + "membersWillReceiveNotification": { + "message": "成員會透過瀏覽器擴充套件接收到有風險登入的通知。" + }, "membersAtRiskCount": { "message": "$COUNT$ 位有風險的成員", "placeholders": { @@ -343,35 +364,41 @@ "reviewNow": { "message": "立即審查" }, + "allCaughtUp": { + "message": "全部完成!" + }, + "noNewApplicationsToReviewAtThisTime": { + "message": "目前沒有新的應用程式可供審查" + }, "prioritizeCriticalApplications": { "message": "優先處理關鍵應用程式" }, - "atRiskItems": { - "message": "有風險的項目" + "selectCriticalApplicationsDescription": { + "message": "選擇對組織最關鍵的應用程式,然後將安全任務指派給成員以供解決。" + }, + "reviewNewApplications": { + "message": "Review new applications" + }, + "reviewNewApplicationsDescription": { + "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + }, + "clickIconToMarkAppAsCritical": { + "message": "點擊星形圖示以將應用程式標記為關鍵" }, "markAsCriticalPlaceholder": { "message": "標記為關鍵功能將在未來更新中實現" }, "applicationReviewSaved": { - "message": "Application review saved" - }, - "applicationsMarkedAsCritical": { - "message": "$COUNT$ applications marked as critical", - "placeholders": { - "count": { - "content": "$1", - "example": "3" - } - } + "message": "已儲存應用程式審查" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "新應用程式已審查" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "儲存審查狀態時發生錯誤" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "請再試一次" }, "unmarkAsCritical": { "message": "取消標記為關鍵" @@ -835,6 +862,9 @@ "favorites": { "message": "我的最愛" }, + "taskSummary": { + "message": "任務摘要" + }, "types": { "message": "類型" }, @@ -1360,7 +1390,7 @@ "message": "使用單一登入" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的組織要求使用單一登入。" }, "welcomeBack": { "message": "歡迎回來" @@ -4409,8 +4439,8 @@ "updateBrowser": { "message": "更新瀏覽器" }, - "generatingYourRiskInsights": { - "message": "正在生成您的風險洞察..." + "generatingYourAccessIntelligence": { + "message": "Generating your Access Intelligence..." }, "riskInsightsRunReport": { "message": "執行報告" @@ -5805,8 +5835,8 @@ "autoConfirmSingleOrgRequired": { "message": "需要啟用單一組織原則。" }, - "autoConfirmSingleOrgRequiredDescription": { - "message": "任何同時屬於多個組織的成員,其存取權都會被撤銷,直到他們離開其他組織為止。" + "autoConfirmSingleOrgRequiredDesc": { + "message": "要使用自動化,所有成員必須僅隸屬於該組織。" }, "autoConfirmSingleOrgExemption": { "message": "單一組織原則將套用到所有角色。" @@ -5872,6 +5902,19 @@ "message": "建立或編輯 Send 時,始終對收件人顯示成員的電子郵件地址。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, + "uriMatchDetectionPolicy": { + "message": "預設的 URI 一致性偵測方式" + }, + "uriMatchDetectionPolicyDesc": { + "message": "決定何時建議登入項目進行自動填入。管理員與擁有者不受此原則限制。" + }, + "uriMatchDetectionOptionsLabel": { + "message": "預設的 URI 一致性偵測方式" + }, + "invalidUriMatchDefaultPolicySetting": { + "message": "請選擇有效的 URI 比對偵測選項。", + "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." + }, "modifiedPolicyId": { "message": "原則 $ID$ 已修改。", "placeholders": { @@ -6525,11 +6568,11 @@ "updateWeakMasterPasswordWarning": { "message": "您的主密碼不符合一個或多個組織政策規定。您必須立即更新您的主密碼才能存取密碼庫。進行此動作將登出您目前的工作階段,需要您重新登入。其他裝置上的工作階段可能持續長達一小時。" }, - "automaticAppLogin": { - "message": "自動登入允許的應用程式使用者" + "automaticAppLoginWithSSO": { + "message": "自動使用SSO登入" }, - "automaticAppLoginDesc": { - "message": "從已設定的身份提供者啟動的應用程式,其登入表單將自動填入並提交。" + "automaticAppLoginWithSSODesc": { + "message": "將SSO的安全和便利拓展至未受管理的應用程式。當使用者用身分識別提供者啟動應用程式時,登入資訊會自動填寫並提交,建立從身分識別提供者到應用程式的一鍵式安全流程。" }, "automaticAppLoginIdpHostLabel": { "message": "身份提供者主機" @@ -7129,6 +7172,9 @@ "selfHostedEnvFormInvalid": { "message": "您必須新增伺服器網域 URL 或至少一個自訂環境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必須使用 HTTPS。" + }, "apiUrl": { "message": "API 伺服器網址" }, @@ -7301,6 +7347,9 @@ "accessDenied": { "message": "拒絕存取。您沒有檢視此頁面的權限。" }, + "noPageAccess": { + "message": "您沒有存取頁面的權限" + }, "masterPassword": { "message": "主密碼" }, @@ -9759,6 +9808,9 @@ "assignTasks": { "message": "指派任務" }, + "assignTasksToMembers": { + "message": "指派任務給成員並引導解決" + }, "assignToCollections": { "message": "指派至集合" }, diff --git a/apps/web/src/scss/tailwind.css b/apps/web/src/scss/tailwind.css index 57332e033b9..a6641a2446e 100644 --- a/apps/web/src/scss/tailwind.css +++ b/apps/web/src/scss/tailwind.css @@ -86,11 +86,11 @@ */ @layer components { .tw-h1 { - @apply tw-text-3xl tw-font-semibold; + @apply tw-text-3xl tw-font-medium; } .tw-btn { - @apply tw-font-semibold tw-py-1.5 tw-px-3 tw-rounded-full tw-transition tw-border-2 tw-border-solid tw-text-center tw-no-underline focus:tw-outline-none; + @apply tw-font-medium tw-py-1.5 tw-px-3 tw-rounded-full tw-transition tw-border-2 tw-border-solid tw-text-center tw-no-underline focus:tw-outline-none; } .tw-btn-secondary { @@ -100,7 +100,7 @@ } .tw-link { - @apply tw-font-semibold hover:tw-underline hover:tw-decoration-1; + @apply tw-font-medium hover:tw-underline hover:tw-decoration-1; @apply tw-text-primary-600 hover:tw-text-primary-700 focus-visible:before:tw-ring-primary-600; } diff --git a/apps/web/src/scss/vault-filters.scss b/apps/web/src/scss/vault-filters.scss index 52c85e8f0ae..9edf0be023a 100644 --- a/apps/web/src/scss/vault-filters.scss +++ b/apps/web/src/scss/vault-filters.scss @@ -30,7 +30,7 @@ color: rgb(var(--color-primary-600)); } &.active { - font-weight: bold; + font-weight: 500; } } } @@ -59,7 +59,7 @@ > .filter-buttons { .filter-button { color: rgb(var(--color-primary-600)); - font-weight: bold; + font-weight: 500; } .edit-button { diff --git a/apps/web/src/videos/access-intelligence-assign-tasks-dark.mp4 b/apps/web/src/videos/access-intelligence-assign-tasks-dark.mp4 new file mode 100644 index 00000000000..c982d0e9d3f Binary files /dev/null and b/apps/web/src/videos/access-intelligence-assign-tasks-dark.mp4 differ diff --git a/apps/web/src/videos/access-intelligence-assign-tasks.mp4 b/apps/web/src/videos/access-intelligence-assign-tasks.mp4 new file mode 100644 index 00000000000..d6f5e01ae22 Binary files /dev/null and b/apps/web/src/videos/access-intelligence-assign-tasks.mp4 differ diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/type-guards/risk-insights-type-guards.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/type-guards/risk-insights-type-guards.ts index 68a1594ff5c..c1aa028da1f 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/type-guards/risk-insights-type-guards.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/helpers/type-guards/risk-insights-type-guards.ts @@ -1,3 +1,5 @@ +import { CipherId } from "@bitwarden/common/types/guid"; + import { ApplicationHealthReportDetail, MemberDetails, @@ -10,7 +12,6 @@ import { createValidator, isBoolean, isBoundedString, - isBoundedStringArray, isBoundedStringOrNull, isBoundedPositiveNumber, BOUNDED_ARRAY_MAX_LENGTH, @@ -33,6 +34,10 @@ export const isMemberDetails = createValidator({ }); export const isMemberDetailsArray = createBoundedArrayGuard(isMemberDetails); +export function isCipherId(value: unknown): value is CipherId { + return value == null || isBoundedString(value); +} +export const isCipherIdArray = createBoundedArrayGuard(isCipherId); /** * Type guard to validate ApplicationHealthReportDetail structure * Exported for testability @@ -40,11 +45,11 @@ export const isMemberDetailsArray = createBoundedArrayGuard(isMemberDetails); */ export const isApplicationHealthReportDetail = createValidator({ applicationName: isBoundedString, - atRiskCipherIds: isBoundedStringArray, + atRiskCipherIds: isCipherIdArray, atRiskMemberCount: isBoundedPositiveNumber, atRiskMemberDetails: isMemberDetailsArray, atRiskPasswordCount: isBoundedPositiveNumber, - cipherIds: isBoundedStringArray, + cipherIds: isCipherIdArray, memberCount: isBoundedPositiveNumber, memberDetails: isMemberDetailsArray, passwordCount: isBoundedPositiveNumber, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mocks/mock-data.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mocks/mock-data.ts index 33dd8676223..027ef8fb25d 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mocks/mock-data.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/mocks/mock-data.ts @@ -1,5 +1,6 @@ import { mock } from "jest-mock-extended"; +import { CipherId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -13,11 +14,14 @@ import { PasswordHealthData, } from "../report-models"; +const mockCipherId1 = "cipher-1" as CipherId; +const mockCipherId2 = "cipher-2" as CipherId; + const mockApplication1: ApplicationHealthReportDetail = { applicationName: "application1.com", passwordCount: 2, atRiskPasswordCount: 1, - atRiskCipherIds: ["cipher-1"], + atRiskCipherIds: [mockCipherId1], memberCount: 2, atRiskMemberCount: 1, memberDetails: [ @@ -33,10 +37,10 @@ const mockApplication1: ApplicationHealthReportDetail = { userGuid: "user-id-2", userName: "tom", email: "tom2@application1.com", - cipherId: "cipher-2", + cipherId: mockCipherId2, }, ], - cipherIds: ["cipher-1", "cipher-2"], + cipherIds: [mockCipherId1, mockCipherId2], }; const mockApplication2: ApplicationHealthReportDetail = { diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts index eecd8256c7f..a907dcf6d7b 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/report-models.ts @@ -1,7 +1,7 @@ import { Opaque } from "type-fest"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { OrganizationReportId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationReportId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { BadgeVariant } from "@bitwarden/components"; @@ -79,12 +79,12 @@ export type ApplicationHealthReportDetail = { applicationName: string; passwordCount: number; atRiskPasswordCount: number; - atRiskCipherIds: string[]; + atRiskCipherIds: CipherId[]; memberCount: number; atRiskMemberCount: number; memberDetails: MemberDetails[]; atRiskMemberDetails: MemberDetails[]; - cipherIds: string[]; + cipherIds: CipherId[]; }; // -------------------- Password Health Report Models -------------------- @@ -107,6 +107,17 @@ export const ReportStatus = Object.freeze({ export type ReportStatus = (typeof ReportStatus)[keyof typeof ReportStatus]; +export const ReportProgress = Object.freeze({ + FetchingMembers: 1, + AnalyzingPasswords: 2, + CalculatingRisks: 3, + GeneratingReport: 4, + Saving: 5, + Complete: 6, +} as const); + +export type ReportProgress = (typeof ReportProgress)[keyof typeof ReportProgress]; + export interface RiskInsightsData { id: OrganizationReportId; creationDate: Date; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/api/security-tasks-api.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/api/security-tasks-api.service.ts index 92bb9207453..e81c91a350c 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/api/security-tasks-api.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/api/security-tasks-api.service.ts @@ -1,7 +1,14 @@ import { from, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { OrganizationId } from "@bitwarden/common/types/guid"; +import { + SecurityTask, + SecurityTaskData, + SecurityTaskResponse, + SecurityTaskStatus, +} from "@bitwarden/common/vault/tasks"; export type TaskMetrics = { completedTasks: number; @@ -22,4 +29,29 @@ export class SecurityTasksApiService { return from(dbResponse as Promise); } + + // Could not import from @bitwarden/bit-web + // Copying from /bitwarden_license/bit-web/src/app/vault/services/default-admin-task.service.ts + async getAllTasks( + organizationId: OrganizationId, + status?: SecurityTaskStatus | undefined, + ): Promise { + const queryParams = new URLSearchParams(); + + queryParams.append("organizationId", organizationId); + if (status !== undefined) { + queryParams.append("status", status.toString()); + } + + const r = await this.apiService.send( + "GET", + `/tasks/organization?${queryParams.toString()}`, + null, + true, + true, + ); + const response = new ListResponse(r, SecurityTaskResponse); + + return response.data.map((d) => new SecurityTask(new SecurityTaskData(d))); + } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.spec.ts index 65ee2c8bb74..7bc0862887b 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.spec.ts @@ -28,7 +28,7 @@ describe("PasswordHealthService", () => { auditService.passwordLeaked.mockImplementation((password: string) => Promise.resolve(password === "leaked" ? 2 : 0), ); - service = new PasswordHealthService(passwordStrengthService, auditService); + service = new PasswordHealthService(auditService, passwordStrengthService); // Setup mock data mockValidCipher = mock({ diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.ts index 267c1dc9563..2d94bf828b8 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/password-health.service.ts @@ -14,8 +14,8 @@ import { export class PasswordHealthService { constructor( - private passwordStrengthService: PasswordStrengthServiceAbstraction, private auditService: AuditService, + private passwordStrengthService: PasswordStrengthServiceAbstraction, ) {} /** diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts index 650726628c6..59affad10da 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts @@ -32,7 +32,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { LogService } from "@bitwarden/logging"; @@ -56,6 +56,8 @@ import { OrganizationReportSummary, ReportStatus, ReportState, + ReportProgress, + ApplicationHealthReportDetail, } from "../../models/report-models"; import { MemberCipherDetailsApiService } from "../api/member-cipher-details-api.service"; import { RiskInsightsApiService } from "../api/risk-insights-api.service"; @@ -87,6 +89,10 @@ export class RiskInsightsOrchestratorService { private _hasCiphersSubject$ = new BehaviorSubject(null); hasCiphers$ = this._hasCiphersSubject$.asObservable(); + private _criticalApplicationAtRiskCipherIdsSubject$ = new BehaviorSubject([]); + readonly criticalApplicationAtRiskCipherIds$ = + this._criticalApplicationAtRiskCipherIdsSubject$.asObservable(); + // ------------------------- Report Variables ---------------- private _rawReportDataSubject = new BehaviorSubject({ status: ReportStatus.Initializing, @@ -98,18 +104,28 @@ export class RiskInsightsOrchestratorService { enrichedReportData$ = this._enrichedReportDataSubject.asObservable(); // New applications that haven't been reviewed (reviewedDate === null) - newApplications$: Observable = this.rawReportData$.pipe( + newApplications$: Observable = this.rawReportData$.pipe( map((reportState) => { - if (!reportState.data?.applicationData) { - return []; - } - return reportState.data.applicationData - .filter((app) => app.reviewedDate === null) - .map((app) => app.applicationName); + const reportApplications = reportState.data?.applicationData || []; + + const newApplications = + reportState?.data?.reportData.filter((reportApp) => + reportApplications.some( + (app) => app.applicationName == reportApp.applicationName && app.reviewedDate == null, + ), + ) || []; + return newApplications; + }), + distinctUntilChanged((prev, curr) => { + if (prev.length !== curr.length) { + return false; + } + return prev.every( + (app, i) => + app.applicationName === curr[i].applicationName && + app.atRiskPasswordCount === curr[i].atRiskPasswordCount, + ); }), - distinctUntilChanged( - (prev, curr) => prev.length === curr.length && prev.every((app, i) => app === curr[i]), - ), shareReplay({ bufferSize: 1, refCount: true }), ); @@ -117,6 +133,10 @@ export class RiskInsightsOrchestratorService { private _generateReportTriggerSubject = new BehaviorSubject(false); generatingReport$ = this._generateReportTriggerSubject.asObservable(); + // Report generation progress + private _reportProgressSubject = new BehaviorSubject(null); + reportProgress$ = this._reportProgressSubject.asObservable(); + // --------------------------- Critical Application data --------------------- criticalReportResults$: Observable = of(null); @@ -332,9 +352,12 @@ export class RiskInsightsOrchestratorService { } // Create a set for quick lookup of the new critical apps - const newCriticalAppNamesSet = new Set(criticalApplications); + const newCriticalAppNamesSet = criticalApplications.map((ca) => ({ + applicationName: ca, + isCritical: true, + })); const existingApplicationData = report!.applicationData || []; - const updatedApplicationData = this._mergeApplicationData( + const updatedApplicationData = this._updateApplicationData( existingApplicationData, newCriticalAppNamesSet, ); @@ -443,18 +466,18 @@ export class RiskInsightsOrchestratorService { } /** - * Saves review status for new applications and optionally marks selected ones as critical. - * This method: - * 1. Sets reviewedDate to current date for all applications where reviewedDate === null - * 2. Sets isCritical = true for applications in the selectedCriticalApps array + * Saves review status for new applications and optionally marks + * selected ones as critical * - * @param selectedCriticalApps Array of application names to mark as critical (can be empty) + * @param reviewedApplications Array of application names to mark as reviewed * @returns Observable of updated ReportState */ - saveApplicationReviewStatus$(selectedCriticalApps: string[]): Observable { - this.logService.info("[RiskInsightsOrchestratorService] Saving application review status", { - criticalAppsCount: selectedCriticalApps.length, - }); + saveApplicationReviewStatus$( + reviewedApplications: OrganizationReportApplication[], + ): Observable { + this.logService.info( + `[RiskInsightsOrchestratorService] Saving application review status for ${reviewedApplications.length} applications`, + ); return this.rawReportData$.pipe( take(1), @@ -464,16 +487,43 @@ export class RiskInsightsOrchestratorService { this._userId$.pipe(filter((userId) => !!userId)), ), map(([reportState, organizationDetails, userId]) => { + const report = reportState?.data; + if (!report) { + throwError(() => Error("Tried save reviewed applications without a report")); + } + const existingApplicationData = reportState?.data?.applicationData || []; - const updatedApplicationData = this._updateReviewStatusAndCriticalFlags( + const updatedApplicationData = this._updateApplicationData( existingApplicationData, - selectedCriticalApps, + reviewedApplications, ); + // Updated summary data after changing critical apps + const updatedSummaryData = this.reportService.getApplicationsSummary( + report!.reportData, + updatedApplicationData, + ); + // Used for creating metrics with updated application data + const manualEnrichedApplications = report!.reportData.map( + (application): ApplicationHealthReportDetailEnriched => ({ + ...application, + isMarkedAsCritical: this.reportService.isCriticalApplication( + application, + updatedApplicationData, + ), + }), + ); + // For now, merge the report with the critical marking flag to make the enriched type + // We don't care about the individual ciphers in this instance + // After the report and enriched report types are consolidated, this mapping can be removed + // and the class will expose getCriticalApplications + const metrics = this._getReportMetrics(manualEnrichedApplications, updatedSummaryData); + const updatedState = { ...reportState, data: { ...reportState.data, + summaryData: updatedSummaryData, applicationData: updatedApplicationData, }, } as ReportState; @@ -484,9 +534,9 @@ export class RiskInsightsOrchestratorService { criticalApps: updatedApplicationData.filter((app) => app.isCritical).length, }); - return { reportState, organizationDetails, updatedState, userId }; + return { reportState, organizationDetails, updatedState, userId, metrics }; }), - switchMap(({ reportState, organizationDetails, updatedState, userId }) => { + switchMap(({ reportState, organizationDetails, updatedState, userId, metrics }) => { return from( this.riskInsightsEncryptionService.encryptRiskInsightsReport( { @@ -506,10 +556,11 @@ export class RiskInsightsOrchestratorService { organizationDetails, updatedState, encryptedData, + metrics, })), ); }), - switchMap(({ reportState, organizationDetails, updatedState, encryptedData }) => { + switchMap(({ reportState, organizationDetails, updatedState, encryptedData, metrics }) => { this.logService.debug( `[RiskInsightsOrchestratorService] Persisting review status - report id: ${reportState?.data?.id}`, ); @@ -521,26 +572,44 @@ export class RiskInsightsOrchestratorService { return of({ ...reportState }); } - return this.reportApiService - .updateRiskInsightsApplicationData$( - reportState.data.id, - organizationDetails.organizationId, - { - data: { - applicationData: encryptedData.encryptedApplicationData.toSdk(), - }, + // Update applications data with critical marking + const updateApplicationsCall = this.reportApiService.updateRiskInsightsApplicationData$( + reportState.data.id, + organizationDetails.organizationId, + { + data: { + applicationData: encryptedData.encryptedApplicationData.toSdk(), }, - ) - .pipe( - map(() => updatedState), - catchError((error: unknown) => { - this.logService.error( - "[RiskInsightsOrchestratorService] Failed to save review status", - error, - ); - return of({ ...reportState, error: "Failed to save application review status" }); - }), - ); + }, + ); + + // Update summary after recomputing + const updateSummaryCall = this.reportApiService.updateRiskInsightsSummary$( + reportState.data.id, + organizationDetails.organizationId, + { + data: { + summaryData: encryptedData.encryptedSummaryData.toSdk(), + metrics: metrics.toRiskInsightsMetricsData(), + }, + }, + ); + + return forkJoin([updateApplicationsCall, updateSummaryCall]).pipe( + map(() => updatedState), + tap((finalState) => { + this._flagForUpdatesSubject.next({ + ...finalState, + }); + }), + catchError((error: unknown) => { + this.logService.error( + "[RiskInsightsOrchestratorService] Failed to save review status", + error, + ); + return of({ ...reportState, error: "Failed to save application review status" }); + }), + ); }), ); } @@ -571,19 +640,33 @@ export class RiskInsightsOrchestratorService { organizationId: OrganizationId, userId: UserId, ): Observable { - // Generate the report + // Reset progress at the start + this._reportProgressSubject.next(null); + + this.logService.debug("[RiskInsightsOrchestratorService] Fetching member cipher details"); + this._reportProgressSubject.next(ReportProgress.FetchingMembers); + + // Generate the report - fetch member ciphers and org ciphers in parallel const memberCiphers$ = from( this.memberCipherDetailsApiService.getMemberCipherDetails(organizationId), ).pipe(map((memberCiphers) => flattenMemberDetails(memberCiphers))); - return forkJoin([this._ciphers$.pipe(take(1)), memberCiphers$]).pipe( - tap(() => { - this.logService.debug("[RiskInsightsOrchestratorService] Generating new report"); + // Start the generation pipeline + const reportGeneration$ = forkJoin([this._ciphers$.pipe(take(1)), memberCiphers$]).pipe( + switchMap(([ciphers, memberCiphers]) => { + this.logService.debug("[RiskInsightsOrchestratorService] Analyzing password health"); + this._reportProgressSubject.next(ReportProgress.AnalyzingPasswords); + return this._getCipherHealth(ciphers ?? [], memberCiphers); + }), + map((cipherHealthReports) => { + this.logService.debug("[RiskInsightsOrchestratorService] Calculating risk scores"); + this._reportProgressSubject.next(ReportProgress.CalculatingRisks); + return this.reportService.generateApplicationsReport(cipherHealthReports); + }), + tap(() => { + this.logService.debug("[RiskInsightsOrchestratorService] Generating report data"); + this._reportProgressSubject.next(ReportProgress.GeneratingReport); }), - switchMap(([ciphers, memberCiphers]) => this._getCipherHealth(ciphers ?? [], memberCiphers)), - map((cipherHealthReports) => - this.reportService.generateApplicationsReport(cipherHealthReports), - ), withLatestFrom(this.rawReportData$), map(([report, previousReport]) => { // Update the application data @@ -620,6 +703,8 @@ export class RiskInsightsOrchestratorService { }; }), switchMap(({ report, summary, applications, metrics }) => { + this.logService.debug("[RiskInsightsOrchestratorService] Saving report"); + this._reportProgressSubject.next(ReportProgress.Saving); return this.reportService .saveRiskInsightsReport$(report, summary, applications, metrics, { organizationId, @@ -636,6 +721,10 @@ export class RiskInsightsOrchestratorService { ); }), // Update the running state + tap(() => { + this.logService.debug("[RiskInsightsOrchestratorService] Report generation complete"); + this._reportProgressSubject.next(ReportProgress.Complete); + }), map((mappedResult): ReportState => { const { id, report, summary, applications, contentEncryptionKey } = mappedResult; return { @@ -663,7 +752,9 @@ export class RiskInsightsOrchestratorService { error: null, data: null, }), - ); + ) as Observable; + + return reportGeneration$; } // Calculates the metrics for a report @@ -752,67 +843,40 @@ export class RiskInsightsOrchestratorService { // Updates the existing application data to include critical applications // Does not remove critical applications not in the set - private _mergeApplicationData( + private _updateApplicationData( existingApplications: OrganizationReportApplication[], - criticalApplications: Set, + updatedApplications: (Partial & { applicationName: string })[], ): OrganizationReportApplication[] { - const setToMerge = new Set(criticalApplications); + const arrayToMerge = [...updatedApplications]; const updatedApps = existingApplications.map((app) => { - const foundCritical = setToMerge.has(app.applicationName); + // Check if there is an updated app + const foundUpdatedIndex = arrayToMerge.findIndex( + (ua) => ua.applicationName == app.applicationName, + ); - if (foundCritical) { - setToMerge.delete(app.applicationName); + let foundApp: Partial | null = null; + // Remove the updated app from the list + if (foundUpdatedIndex >= 0) { + foundApp = arrayToMerge[foundUpdatedIndex]; + arrayToMerge.splice(foundUpdatedIndex, 1); } - return { - ...app, - isCritical: foundCritical || app.isCritical, + applicationName: app.applicationName, + isCritical: foundApp?.isCritical || app.isCritical, + reviewedDate: foundApp?.reviewedDate || app.reviewedDate, }; }); - setToMerge.forEach((applicationName) => { - updatedApps.push({ - applicationName, - isCritical: true, + const newElements: OrganizationReportApplication[] = arrayToMerge.map( + (newApp): OrganizationReportApplication => ({ + applicationName: newApp.applicationName, + isCritical: newApp.isCritical ?? false, reviewedDate: null, - }); - }); + }), + ); - return updatedApps; - } - - /** - * Updates review status and critical flags for applications. - * Sets reviewedDate for all apps with null reviewedDate. - * Sets isCritical flag for apps in the criticalApplications array. - * - * @param existingApplications Current application data - * @param criticalApplications Array of application names to mark as critical - * @returns Updated application data with review dates and critical flags - */ - private _updateReviewStatusAndCriticalFlags( - existingApplications: OrganizationReportApplication[], - criticalApplications: string[], - ): OrganizationReportApplication[] { - const criticalSet = new Set(criticalApplications); - const currentDate = new Date(); - - return existingApplications.map((app) => { - const shouldMarkCritical = criticalSet.has(app.applicationName); - const needsReviewDate = app.reviewedDate === null; - - // Only create new object if changes are needed - if (needsReviewDate || shouldMarkCritical) { - return { - ...app, - reviewedDate: needsReviewDate ? currentDate : app.reviewedDate, - isCritical: shouldMarkCritical || app.isCritical, - }; - } - - return app; - }); + return updatedApps.concat(newElements); } // Toggles the isCritical flag on applications via criticalApplicationName @@ -1090,10 +1154,42 @@ export class RiskInsightsOrchestratorService { this._reportStateSubscription = mergedReportState$ .pipe(takeUntil(this._destroy$)) .subscribe((state) => { + // Update the raw report data subject this._rawReportDataSubject.next(state.reportState); + + // Update the critical application at risk cipher ids for exposure + const reportState = state.reportState?.data; + if (reportState) { + const criticalApplicationAtRiskCipherIds = this._getCriticalApplicationCipherIds( + reportState.reportData || [], + reportState.applicationData || [], + ); + this._criticalApplicationAtRiskCipherIdsSubject$.next(criticalApplicationAtRiskCipherIds); + } }); } + // Gets the unique cipher IDs that are marked at risk in critical applications + private _getCriticalApplicationCipherIds( + applications: ApplicationHealthReportDetail[], + applicationData: OrganizationReportApplication[], + ): CipherId[] { + const foundCipherIds = applications + .map((app) => { + const isCriticalApplication = this.reportService.isCriticalApplication( + app, + applicationData, + ); + return isCriticalApplication ? app.atRiskCipherIds : []; + }) + .flat(); + + // Use a set to ensure uniqueness + const uniqueCipherIds = new Set([...foundCipherIds]); + + return [...uniqueCipherIds]; + } + // Setup the user ID observable to track the current user private _setupUserId() { // Watch userId changes diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts index d49d7a4a40f..94c9c85f955 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts @@ -1,7 +1,12 @@ import { catchError, EMPTY, from, map, Observable, of, switchMap, throwError } from "rxjs"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { OrganizationId, OrganizationReportId, UserId } from "@bitwarden/common/types/guid"; +import { + CipherId, + OrganizationId, + OrganizationReportId, + UserId, +} from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { getUniqueMembers } from "../../helpers/risk-insights-data-mappers"; @@ -63,7 +68,7 @@ export class RiskInsightsReportService { ): Map { const cipherMap = new Map(); applications.forEach((app) => { - const filteredCiphers = ciphers.filter((c) => app.cipherIds.includes(c.id)); + const filteredCiphers = ciphers.filter((c) => app.cipherIds.includes(c.id as CipherId)); cipherMap.set(app.applicationName, filteredCiphers); }); return cipherMap; @@ -346,7 +351,7 @@ export class RiskInsightsReportService { ): ApplicationHealthReportDetail { return { applicationName: application, - cipherIds: [cipherReport.cipher.id], + cipherIds: [cipherReport.cipher.id as CipherId], passwordCount: 1, memberDetails: [...cipherReport.cipherMembers], memberCount: cipherReport.cipherMembers.length, @@ -367,7 +372,7 @@ export class RiskInsightsReportService { memberDetails: getUniqueMembers( existingReport.memberDetails.concat(newCipherReport.cipherMembers), ), - cipherIds: existingReport.cipherIds.concat(newCipherReport.cipher.id), + cipherIds: existingReport.cipherIds.concat(newCipherReport.cipher.id as CipherId), }; } @@ -377,7 +382,7 @@ export class RiskInsightsReportService { ); return { atRiskPasswordCount: report.atRiskPasswordCount + 1, - atRiskCipherIds: report.atRiskCipherIds.concat(cipherReport.cipher.id), + atRiskCipherIds: report.atRiskCipherIds.concat(cipherReport.cipher.id as CipherId), atRiskMemberDetails, atRiskMemberCount: atRiskMemberDetails.length, }; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/all-activities.service.ts index 22d8e24562d..2111049ce52 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/all-activities.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/all-activities.service.ts @@ -11,7 +11,6 @@ export class AllActivitiesService { /// and critical applications. /// Going forward, this class can be simplified by using the RiskInsightsDataService /// as it contains the application summary data. - private reportSummarySubject$ = new BehaviorSubject({ totalMemberCount: 0, totalCriticalMemberCount: 0, @@ -31,12 +30,8 @@ export class AllActivitiesService { private atRiskPasswordsCountSubject$ = new BehaviorSubject(0); atRiskPasswordsCount$ = this.atRiskPasswordsCountSubject$.asObservable(); - private passwordChangeProgressMetricHasProgressBarSubject$ = new BehaviorSubject(false); - passwordChangeProgressMetricHasProgressBar$ = - this.passwordChangeProgressMetricHasProgressBarSubject$.asObservable(); - - private taskCreatedCountSubject$ = new BehaviorSubject(0); - taskCreatedCount$ = this.taskCreatedCountSubject$.asObservable(); + private extendPasswordChangeWidgetSubject$ = new BehaviorSubject(false); + extendPasswordChangeWidget$ = this.extendPasswordChangeWidgetSubject$.asObservable(); constructor(private dataService: RiskInsightsDataService) { // All application summary changes @@ -91,11 +86,7 @@ export class AllActivitiesService { this.allApplicationsDetailsSubject$.next(applications); } - setPasswordChangeProgressMetricHasProgressBar(hasProgressBar: boolean) { - this.passwordChangeProgressMetricHasProgressBarSubject$.next(hasProgressBar); - } - - setTaskCreatedCount(count: number) { - this.taskCreatedCountSubject$.next(count); + setExtendPasswordWidget(hasProgressBar: boolean) { + this.extendPasswordChangeWidgetSubject$.next(hasProgressBar); } } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts index 2dc669f5727..7b9255ca821 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts @@ -1,7 +1,7 @@ import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; import { distinctUntilChanged, map } from "rxjs/operators"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { getAtRiskApplicationList, getAtRiskMemberList } from "../../helpers"; import { @@ -10,6 +10,9 @@ import { DrawerType, RiskInsightsEnrichedData, ReportStatus, + ReportProgress, + ApplicationHealthReportDetail, + OrganizationReportApplication, } from "../../models"; import { RiskInsightsOrchestratorService } from "../domain/risk-insights-orchestrator.service"; @@ -36,9 +39,11 @@ export class RiskInsightsDataService { readonly isGeneratingReport$: Observable = of(false); readonly criticalReportResults$: Observable = of(null); readonly hasCiphers$: Observable = of(null); + readonly criticalApplicationAtRiskCipherIds$: Observable = of([]); + readonly reportProgress$: Observable = of(null); // New applications that need review (reviewedDate === null) - readonly newApplications$: Observable = of([]); + readonly newApplications$: Observable = of([]); // ------------------------- Drawer Variables --------------------- // Drawer variables unified into a single BehaviorSubject @@ -60,6 +65,9 @@ export class RiskInsightsDataService { this.enrichedReportData$ = this.orchestrator.enrichedReportData$; this.criticalReportResults$ = this.orchestrator.criticalReportResults$; this.newApplications$ = this.orchestrator.newApplications$; + this.criticalApplicationAtRiskCipherIds$ = + this.orchestrator.criticalApplicationAtRiskCipherIds$; + this.reportProgress$ = this.orchestrator.reportProgress$; this.hasCiphers$ = this.orchestrator.hasCiphers$.pipe(distinctUntilChanged()); @@ -257,7 +265,7 @@ export class RiskInsightsDataService { return this.orchestrator.removeCriticalApplication$(hostname); } - saveApplicationReviewStatus(selectedCriticalApps: string[]) { + saveApplicationReviewStatus(selectedCriticalApps: OrganizationReportApplication[]) { return this.orchestrator.saveApplicationReviewStatus$(selectedCriticalApps); } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts index 85110a5af21..eb82dce4383 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/automatic-app-login.component.ts @@ -11,8 +11,8 @@ import { import { SharedModule } from "@bitwarden/web-vault/app/shared"; export class AutomaticAppLoginPolicy extends BasePolicyEditDefinition { - name = "automaticAppLogin"; - description = "automaticAppLoginDesc"; + name = "automaticAppLoginWithSSO"; + description = "automaticAppLoginWithSSODesc"; type = PolicyType.AutomaticAppLogIn; component = AutomaticAppLoginPolicyComponent; } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html index c11b23db9fb..fc3d4e9e628 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-client-dialog.component.html @@ -1,6 +1,6 @@
    -
    + {{ "newClientOrganization" | i18n }}
    @@ -22,16 +22,16 @@
    {{ "selected" | i18n }}
    -

    {{ planCard.name }}

    - {{ +

    {{ planCard.name }}

    + {{ planCard.getMonthlyCost() | currency: "$" }} - / {{ planCard.getTimePerMemberLabel() | i18n }}
    diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html index 6d7d4b2f18d..bc4b4674201 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/manage-client-name-dialog.component.html @@ -1,6 +1,6 @@ - + {{ "updateName" | i18n }} {{ dialogParams.organization.name }} diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html index 3892892a9c6..bc209ead2bd 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.html @@ -18,7 +18,7 @@

    {{ providerName }} - {{ email }} + {{ email }}

    {{ "joinProviderDesc" | i18n }}


    diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html index 07ccd997b96..e0b29dffeb8 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.html @@ -67,7 +67,7 @@ (change)="dataSource.checkAllFilteredUsers($any($event.target).checked)" id="selectAll" /> -

    - Total: {{ totalCost | currency: "$" }} / + Total: {{ totalCost | currency: "$" }} / {{ getBillingCadenceLabel(activePlans.length > 0 ? activePlans[0] : null) | i18n }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts index 2e3c53d8d9f..4bdc8e25047 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts @@ -6,17 +6,20 @@ import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-con import { RiskInsightsComponent } from "./risk-insights.component"; const routes: Routes = [ - { path: "", pathMatch: "full", redirectTo: "risk-insights" }, { - path: "risk-insights", - canActivate: [ - organizationPermissionsGuard((org) => org.useRiskInsights && org.canAccessReports), - ], + path: "", + canActivate: [organizationPermissionsGuard((org) => org.canAccessReports)], component: RiskInsightsComponent, data: { - titleId: "RiskInsights", + titleId: "accessIntelligence", }, }, + { + path: "risk-insights", + redirectTo: "", + pathMatch: "full", + // Backwards compatibility: redirect old "risk-insights" route to new base route + }, ]; @NgModule({ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts index 2cb9140f174..5592e4cc546 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence.module.ts @@ -20,37 +20,52 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { KeyGenerationService } from "@bitwarden/common/key-management/crypto"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { ToastService } from "@bitwarden/components"; import { KeyService } from "@bitwarden/key-management"; import { LogService } from "@bitwarden/logging"; import { DefaultAdminTaskService } from "../../vault/services/default-admin-task.service"; import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module"; +import { NewApplicationsDialogComponent } from "./activity/application-review-dialog/new-applications-dialog.component"; import { RiskInsightsComponent } from "./risk-insights.component"; import { AccessIntelligenceSecurityTasksService } from "./shared/security-tasks.service"; @NgModule({ - imports: [RiskInsightsComponent, AccessIntelligenceRoutingModule], + imports: [RiskInsightsComponent, AccessIntelligenceRoutingModule, NewApplicationsDialogComponent], providers: [ + safeProvider({ + provide: CriticalAppsApiService, + useClass: CriticalAppsApiService, + deps: [ApiService], + }), safeProvider({ provide: MemberCipherDetailsApiService, useClass: MemberCipherDetailsApiService, deps: [ApiService], }), - safeProvider({ - provide: PasswordHealthService, - useClass: PasswordHealthService, - deps: [PasswordStrengthServiceAbstraction, AuditService], - }), safeProvider({ provide: RiskInsightsApiService, useClass: RiskInsightsApiService, deps: [ApiService], }), + safeProvider({ + provide: SecurityTasksApiService, + useClass: SecurityTasksApiService, + deps: [ApiService], + }), + safeProvider(DefaultAdminTaskService), + safeProvider({ + provide: AccessIntelligenceSecurityTasksService, + useClass: AccessIntelligenceSecurityTasksService, + deps: [DefaultAdminTaskService, SecurityTasksApiService], + }), + safeProvider({ + provide: PasswordHealthService, + useClass: PasswordHealthService, + deps: [AuditService, PasswordStrengthServiceAbstraction], + }), safeProvider({ provide: RiskInsightsReportService, useClass: RiskInsightsReportService, @@ -84,26 +99,11 @@ import { AccessIntelligenceSecurityTasksService } from "./shared/security-tasks. useClass: CriticalAppsService, deps: [KeyService, EncryptService, CriticalAppsApiService], }), - safeProvider({ - provide: CriticalAppsApiService, - useClass: CriticalAppsApiService, - deps: [ApiService], - }), safeProvider({ provide: AllActivitiesService, useClass: AllActivitiesService, deps: [RiskInsightsDataService], }), - safeProvider({ - provide: SecurityTasksApiService, - useClass: SecurityTasksApiService, - deps: [ApiService], - }), - safeProvider({ - provide: AccessIntelligenceSecurityTasksService, - useClass: AccessIntelligenceSecurityTasksService, - deps: [AllActivitiesService, DefaultAdminTaskService, ToastService, I18nService], - }), ], }) export class AccessIntelligenceModule {} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html index 674bc0b5c62..4b765a5502e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html @@ -5,75 +5,80 @@ {{ "passwordChangeProgress" | i18n }} - @if (renderMode === renderModes.noCriticalApps) { -
    - {{ "assignMembersTasksToMonitorProgress" | i18n }} -
    - -
    - {{ "onceYouReviewApps" | i18n }} -
    - } - - @if (renderMode === renderModes.criticalAppsWithAtRiskAppsAndNoTasks) { -
    - {{ "assignMembersTasksToMonitorProgress" | i18n }} -
    - -
    - {{ - hasExistingTasks - ? ("newPasswordsAtRisk" | i18n: newAtRiskPasswordsCount) - : ("countOfAtRiskPasswords" | i18n: atRiskPasswordsCount) - }} -
    - -
    - -
    - } - - @if (renderMode === renderModes.criticalAppsWithAtRiskAppsAndTasks) { -
    - {{ "percentageCompleted" | i18n: completedPercent }} -
    - -
    - {{ - "securityTasksCompleted" | i18n: completedTasksCount : totalTasksCount - }} -
    - -
    -
    -
    {{ completedTasksCount }}
    -
    {{ totalTasksCount }}
    + @switch (currentView()) { + @case (PasswordChangeViewEnum.EMPTY) { +
    + {{ "assignMembersTasksToMonitorProgress" | i18n }}
    -
    - - - - +
    + {{ "onceYouReviewApps" | i18n }} +
    + } + + @case (PasswordChangeViewEnum.NO_TASKS_ASSIGNED) { +
    + {{ "assignMembersTasksToMonitorProgress" | i18n }} +
    + +
    + {{ + "countOfAtRiskPasswords" | i18n: atRiskPasswordCount() + }} +
    + + @if (atRiskPasswordCount() > 0) { +
    + +
    + } + } + + @case (PasswordChangeViewEnum.NEW_TASKS_AVAILABLE) { +
    + {{ "assignMembersTasksToMonitorProgress" | i18n }} +
    + +
    + {{ "newPasswordsAtRisk" | i18n: atRiskPasswordCount() }} +
    + +
    + +
    + } + + @case (PasswordChangeViewEnum.PROGRESS) { +
    + {{ "percentageCompleted" | i18n: completedTasksPercent() }} +
    + +
    + {{ + "securityTasksCompleted" | i18n: completedTasksCount() : tasksCount() + }} +
    + +
    +
    +
    {{ completedTasksCount() }}
    +
    {{ tasksCount() }}
    +
    +
    + + + } }
    diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts index 5c03534720e..509b3e1314a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts @@ -1,197 +1,169 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, - ChangeDetectorRef, Component, DestroyRef, + Injector, OnInit, + Signal, + computed, + effect, inject, + input, + signal, } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute } from "@angular/router"; -import { switchMap, of, BehaviorSubject, combineLatest } from "rxjs"; +import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AllActivitiesService, - ApplicationHealthReportDetailEnriched, - SecurityTasksApiService, - TaskMetrics, - OrganizationReportSummary, + RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { OrganizationId } from "@bitwarden/common/types/guid"; -import { ButtonModule, ProgressModule, TypographyModule } from "@bitwarden/components"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTask, SecurityTaskStatus } from "@bitwarden/common/vault/tasks"; +import { + ButtonModule, + ProgressModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; -import { DefaultAdminTaskService } from "../../../../vault/services/default-admin-task.service"; -import { RenderMode } from "../../models/activity.models"; import { AccessIntelligenceSecurityTasksService } from "../../shared/security-tasks.service"; +export const PasswordChangeView = { + EMPTY: "empty", + NO_TASKS_ASSIGNED: "noTasksAssigned", + NEW_TASKS_AVAILABLE: "newTasks", + PROGRESS: "progress", +} as const; + +export type PasswordChangeView = (typeof PasswordChangeView)[keyof typeof PasswordChangeView]; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: "dirt-password-change-metric", imports: [CommonModule, TypographyModule, JslibModule, ProgressModule, ButtonModule], templateUrl: "./password-change-metric.component.html", - providers: [AccessIntelligenceSecurityTasksService, DefaultAdminTaskService], }) export class PasswordChangeMetricComponent implements OnInit { + PasswordChangeViewEnum = PasswordChangeView; + private destroyRef = inject(DestroyRef); - protected taskMetrics$ = new BehaviorSubject({ totalTasks: 0, completedTasks: 0 }); - private completedTasks: number = 0; - private totalTasks: number = 0; - private allApplicationsDetails: ApplicationHealthReportDetailEnriched[] = []; + // Inputs + // Prefer component input since route param controls UI state + readonly organizationId = input.required(); - atRiskAppsCount: number = 0; - atRiskPasswordsCount: number = 0; - private organizationId!: OrganizationId; - renderMode: RenderMode = "noCriticalApps"; + // Signal states + private readonly _tasks: Signal = signal([]); + private readonly _atRiskCipherIds: Signal = signal([]); + private readonly _hasCriticalApplications: Signal = signal(false); - // Computed properties (formerly getters) - updated when data changes - protected completedPercent = 0; - protected completedTasksCount = 0; - protected totalTasksCount = 0; - protected canAssignTasks = false; - protected hasExistingTasks = false; - protected newAtRiskPasswordsCount = 0; + // Computed properties + readonly tasksCount = computed(() => this._tasks().length); + readonly completedTasksCount = computed( + () => this._tasks().filter((task) => task.status === SecurityTaskStatus.Completed).length, + ); + readonly uncompletedTasksCount = computed( + () => this._tasks().filter((task) => task.status == SecurityTaskStatus.Pending).length, + ); + readonly completedTasksPercent = computed(() => { + const total = this.tasksCount(); + // Account for case where there are no tasks to avoid NaN + return total > 0 ? Math.round((this.completedTasksCount() / total) * 100) : 0; + }); + + readonly atRiskPasswordCount = computed(() => { + const atRiskIds = this._atRiskCipherIds(); + const tasks = this._tasks(); + + if (tasks.length === 0) { + return atRiskIds.length; + } + + const assignedIdSet = new Set(tasks.map((task) => task.cipherId)); + const unassignedIds = atRiskIds.filter((id) => !assignedIdSet.has(id)); + + return unassignedIds.length; + }); + + readonly currentView = computed(() => { + if (!this._hasCriticalApplications()) { + return PasswordChangeView.EMPTY; + } + if (this.tasksCount() === 0) { + return PasswordChangeView.NO_TASKS_ASSIGNED; + } + if (this.atRiskPasswordCount() > 0) { + return PasswordChangeView.NEW_TASKS_AVAILABLE; + } + return PasswordChangeView.PROGRESS; + }); constructor( - private activatedRoute: ActivatedRoute, - private securityTasksApiService: SecurityTasksApiService, private allActivitiesService: AllActivitiesService, - protected accessIntelligenceSecurityTasksService: AccessIntelligenceSecurityTasksService, - private cdr: ChangeDetectorRef, - ) {} + private i18nService: I18nService, + private injector: Injector, + private riskInsightsDataService: RiskInsightsDataService, + protected securityTasksService: AccessIntelligenceSecurityTasksService, + private toastService: ToastService, + ) { + // Setup the _tasks signal by manually passing in the injector + this._tasks = toSignal(this.securityTasksService.tasks$, { + initialValue: [], + injector: this.injector, + }); + // Setup the _atRiskCipherIds signal by manually passing in the injector + this._atRiskCipherIds = toSignal( + this.riskInsightsDataService.criticalApplicationAtRiskCipherIds$, + { + initialValue: [], + injector: this.injector, + }, + ); + + this._hasCriticalApplications = toSignal( + this.riskInsightsDataService.criticalReportResults$.pipe( + takeUntilDestroyed(this.destroyRef), + map((report) => { + return report != null && (report.reportData?.length ?? 0) > 0; + }), + ), + { + initialValue: false, + injector: this.injector, + }, + ); + + effect(() => { + const isShowingProgress = this.currentView() === PasswordChangeView.PROGRESS; + this.allActivitiesService.setExtendPasswordWidget(isShowingProgress); + }); + } async ngOnInit(): Promise { - combineLatest([this.activatedRoute.paramMap, this.allActivitiesService.taskCreatedCount$]) - .pipe( - switchMap(([params, _]) => { - const orgId = params.get("organizationId"); - if (orgId) { - this.organizationId = orgId as OrganizationId; - return this.securityTasksApiService.getTaskMetrics(this.organizationId); - } - return of({ totalTasks: 0, completedTasks: 0 }); - }), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe((metrics) => { - this.taskMetrics$.next(metrics); - this.cdr.markForCheck(); - }); - - combineLatest([ - this.taskMetrics$, - this.allActivitiesService.reportSummary$, - this.allActivitiesService.atRiskPasswordsCount$, - this.allActivitiesService.allApplicationsDetails$, - ]) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(([taskMetrics, summary, atRiskPasswordsCount, allApplicationsDetails]) => { - this.atRiskAppsCount = summary.totalCriticalAtRiskApplicationCount; - this.atRiskPasswordsCount = atRiskPasswordsCount; - this.completedTasks = taskMetrics.completedTasks; - this.totalTasks = taskMetrics.totalTasks; - this.allApplicationsDetails = allApplicationsDetails; - - // Determine render mode based on state - this.renderMode = this.determineRenderMode(summary, taskMetrics, atRiskPasswordsCount); - - this.allActivitiesService.setPasswordChangeProgressMetricHasProgressBar( - this.renderMode === RenderMode.criticalAppsWithAtRiskAppsAndTasks, - ); - - // Update all computed properties when data changes - this.updateComputedProperties(); - - this.cdr.markForCheck(); - }); - } - - private determineRenderMode( - summary: OrganizationReportSummary, - taskMetrics: TaskMetrics, - atRiskPasswordsCount: number, - ): RenderMode { - // State 1: No critical apps setup - if (summary.totalCriticalApplicationCount === 0) { - return RenderMode.noCriticalApps; - } - - // State 2: Critical apps with at-risk passwords but no tasks assigned yet - // OR tasks exist but NEW at-risk passwords detected (more at-risk passwords than tasks) - if ( - summary.totalCriticalApplicationCount > 0 && - (taskMetrics.totalTasks === 0 || atRiskPasswordsCount > taskMetrics.totalTasks) - ) { - return RenderMode.criticalAppsWithAtRiskAppsAndNoTasks; - } - - // State 3: Critical apps with at-risk apps and tasks (progress tracking) - if ( - summary.totalCriticalApplicationCount > 0 && - taskMetrics.totalTasks > 0 && - atRiskPasswordsCount <= taskMetrics.totalTasks - ) { - return RenderMode.criticalAppsWithAtRiskAppsAndTasks; - } - - // Default to no critical apps - return RenderMode.noCriticalApps; - } - - /** - * Updates all computed properties based on current state. - * Called whenever data changes to avoid recalculation on every change detection cycle. - */ - private updateComputedProperties(): void { - // Calculate completion percentage - this.completedPercent = - this.totalTasks === 0 ? 0 : Math.round((this.completedTasks / this.totalTasks) * 100); - - // Calculate completed tasks count based on render mode - switch (this.renderMode) { - case RenderMode.noCriticalApps: - case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: - this.completedTasksCount = 0; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndTasks: - this.completedTasksCount = this.completedTasks; - break; - default: - this.completedTasksCount = 0; - } - - // Calculate total tasks count based on render mode - switch (this.renderMode) { - case RenderMode.noCriticalApps: - this.totalTasksCount = 0; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: - this.totalTasksCount = this.atRiskAppsCount; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndTasks: - this.totalTasksCount = this.totalTasks; - break; - default: - this.totalTasksCount = 0; - } - - // Calculate flags and counts - this.canAssignTasks = this.atRiskPasswordsCount > this.totalTasks; - this.hasExistingTasks = this.totalTasks > 0; - this.newAtRiskPasswordsCount = - this.atRiskPasswordsCount > this.totalTasks ? this.atRiskPasswordsCount - this.totalTasks : 0; - } - - get renderModes() { - return RenderMode; + await this.securityTasksService.loadTasks(this.organizationId()); } async assignTasks() { - await this.accessIntelligenceSecurityTasksService.assignTasks( - this.organizationId, - this.allApplicationsDetails.filter((app) => app.isMarkedAsCritical), - ); + try { + await this.securityTasksService.requestPasswordChangeForCriticalApplications( + this.organizationId(), + this._atRiskCipherIds(), + ); + this.toastService.showToast({ + message: this.i18nService.t("notifiedMembers"), + variant: "success", + title: this.i18nService.t("success"), + }); + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + } } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index 8cdb927ab65..d8ad785ff14 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -4,8 +4,10 @@